Изобретаем успех: софт и стартапы
298,44
рейтинг
2 декабря 2015 в 10:40

Разработка → Пол Грэм: «Месть ботанов», часть 3

Продолжаем перевод эссе и книги Пола Грэма «Хакеры и Художники».

Недавно на Хабре промелькивала статья «Нам нужны не столь мощные языки программирования» с «откровенями 60х годов». Чтобы самому разобраться в этом вопросе, нужно понять, что такое «мощность», про это Грэм рассказывает в конце главы. Так же в статье Грэм поднимает вопрос столкновения интересов крутых программистов и посредственных начальников в выборе языка программирования. Я задал пару вопросов специалистам компании Edison:

— Как вы поступаете, если заказчик требует написать что-то на одном языке, а вы рекомендуете для пользы дела другой язык?
— Мы предлагаем заказчику использование того или иного языка и фреймворка исходя из целесообразности для проекта. Если по той или иной причине заказчик, выслушав наши доводы, настаивает на решении задачи на каком-то другом языке или платформе, то мы считаем это частью постановки задачи и решаем её именно так.

— «Невежественный начальник» vs «отличный программист» — как у вас решается подобный вопрос?
— Это вопрос ответственности. Долг программиста — продумать и донести до начальника технически лучшее решение, но если начальник по той или иной причине всё равно настаивает на своей точке зрения, то хороший программист, как и любой подчинённый, должен послушаться, а не доказывать с пеной у рта свою правоту и саботировать работу. Ответственность за решение в этом случае лежит на начальнике.


«Мы гонялись за С++ программистами. Нам удалось перетащить их целую кучу на полпути к Lisp.»
Гай Стил, соавтор Java спецификации.

Оригинал — Revenge of the Nerds, Май 2002
За перевод спасибо Щёкотовой Яне.

Начало: Пол Грэм: «Месть ботанов», часть 1
Продолжение: Пол Грэм: «Месть ботанов», часть 2

Часть третья



Центростремительные силы

Я и не утверждаю, что при использовании нестандартных технологий не требуется никаких затрат. Не такими уж беспочвенными оказались опасения нашего начальника-профана. Но без осознания всех рисков он склонен к их преувеличению.

Мне на ум приходят три проблемы, которые могут возникнуть при использовании менее популярных языков программирования. Ваши программы могут некорректно работать с программами, написанными на других языках. В вашем распоряжении может быть гораздо меньше различных библиотек. Вы также столкнетесь с трудностями при найме программистов.

Насколько страшна каждая из перечисленных проблем? Важность первой варьируется в зависимости от того, есть ли у вас контроль над всей системой. Если вы создаете ПО, которое будет запускаться на удаленной пользовательской машине, управляющей кадиллаком, на закрытой операционной системе (не буду приводить названия), то, возможно, есть преимущества в том, чтобы писать свое приложение на том же языке, на котором написана сама ОС. Но, если вы держите под контролем всю систему и владеете исходными кодами всех ее частей, как, вероятно, в случае с ITA, то вы можете использовать любой язык, какой захотите. При возникновении несовместимости, вы можете самостоятельно это исправить.

В серверных приложениях вы можете выйти из положения посредством использования передовых технологий, и я думаю, что это главная причина того, что Джонатан Эриксон называет «возрождением языка программирования». Вот почему мы узнаем о новых языках, таких как Perl и Python. Мы знаем про эти языки не потому, что люди используют их для написания Windows приложений, а потому, что люди используют их на серверах. И, поскольку ПО переходит с десктопов на серверную платформу (будущее, с которым даже Microsoft, кажется, смирился), вынужденная необходимость использования попсовых технологий со временем сойдет на нет.

Что касается библиотек, то их важность также зависит от самого приложения. Для менее требовательных задач наличие библиотек может перевесить подлинную мощь языка. Так где же эта точка безубыточности? Это непросто точно сформулировать, но где бы она ни находилась, в ней будет наблюдаться нехватка всего того, под чем подразумевается приложение. Если фирма решилась выйти на рынок ПО, и в ней люди занимаются созданием приложения, которое будет позиционироваться как один из ее продуктов, тогда эта компания, вероятнее всего, вовлечет в работу нескольких опытных специалистов, и ей потребуется минимум 6 месяцев, чтобы создать его. В проектах таких масштабов мощные языки программирования начинают перевешивать удобство наличия библиотек.

Третья причина беспокойства начальства, трудность в найме программистов, является ложным опасением. В конце концов, сколько специалистов вам нужно нанять? Конечно, сейчас нам всем известно, что лучше всего ПО разрабатывается командами до десяти человек. И у вас не должно возникнуть проблем при найме сотрудников в таком масштабе для любого языка программирования, включая тот, о котором некоторые едва ли слышали. Если вы не можете найти десять Lisp программистов, тогда ваша фирма, вероятнее всего, расположена не в том городе, чтобы заниматься там разработкой ПО.

На самом деле, выбор более мощного языка программирования немного снизит разрмер необходимой вам команды, потому что (а) если вы используете более мощный язык, то вам, вероятно, не понадобится так много специалистов, а также (б) программисты, работающие с более продвинутыми языками, являются более умными.

Я не утверждаю, что вы не будете вынуждены использовать то, под чем понимают «стандартные» технологии. В Viaweb (ныне Yahoo Store) мы вызвали всеобщее недоумение среди венчурных капиталистов и потенциальных покупателей фирмы самим фактом использования Lisp. Но мы также всех удивили использованием в качестве серверов универсальных коробочных сборок Intel вместо серверов «профессионального уровня» таких фирм как Sun, эксплуатацией, в те времена непонятного, опенсорсного варианта Unix-подобной системы FreeBSD вместо настоящих коммерческих решений наподобие Windows NT, игнорированием предполагаемого стандарта электронной коммерции SET, который сейчас никто даже и не вспомнит, и прочими вещами.

Нельзя позволять управленцам принимать технические решения за вас. Встревожило ли некоторых потенциальных покупателей фирмы то, что мы использовали Lisp? Некоторых да, слегка. Но если бы мы не работали с Lisp, мы бы не смогли создать ПО, которое бы пробудило в них желание нас купить. То, что казалось для них аномальным, было, на самом деле, причиной и следствием.

Если вы начинаете startup, то не проектируйте свой продукт таким образом, чтобы понравиться инвесторам или потенциальным покупателям. Разрабатывайте свой продукт так, чтобы понравиться пользователям. Если вы завоюете пользователей, то все остальное приложится. А если этого сделать не удастся, то никому не будет и дела до того, насколько угодно правоверными были ваши технологические решения.

Цена посредственности

Сколько вы теряете при использовании менее мощного языка программирования? На этот счет существуют некоторые данные.

Самой удобной для измерения мерой является, скорее всего, размер кода. Цель высокоуровневых языков состоит в том, чтобы предоставить вам большее число абстракций, более крупные кирпичики, так сказать, чтобы вы обходились без их огромного количества в ходе построения стены определенных размеров. Поэтому, чем мощнее язык, тем короче программа (не только по количеству символов, конечно, но и по числу отдельных элементов).

Как же более мощный язык программирования позволяет писать более короткие программы? Одним из подходов, которым вы можете воспользоваться, если язык позволит, является восходящее программирование. Вместо того, чтобы просто писать ваше приложение на базовом языке, вы поверх базового языка конструируете язык для создания программ подобно вашей, а затем уже на нем пишете вашу программу. Скомпонованный таким образом код может быть гораздо короче, чем если бы вы писали всю вашу программу на базовом языке. На самом деле именно так и работают большинство алгоритмов сжатия. Такую программу будет легче модифицировать, т.к. в большинстве случаев языковой слой вообще не придется изменять.

Объем кода важен, т.к. время на написание программы зависит в основном от длины кода. Если бы ваша программа была в три раза длиннее на другом языке программирования, то вам бы потребовалось написать в три раза больше исходных текстов. И с этим не справиться с помощью найма большего числа людей, потому что по достижению определенного размера дополнительный найм рабочей силы превратится в чистый убыток. Фред Брукс описал этот феномен в своей известной книге «Мифический человеко-месяц», и все, что я видел, лишь подтверждает его слова.

Так на сколько же короче будут ваши программы, если вы будете писать их на Lisp? Чаще всего при сравнении Lisp с С, например, отмечают примерно семи-десятикратное уменьшение. Но в недавней заметке про ITA в журнале New Architect указано, что «одна строка на Lisp может заменить 20 строк на С», а поскольку эта статья сплошь состоит из цитат президента фирмы ITA, я допускаю, что они это число узнали от самой ITA. Если это так, тогда этому утверждению можно доверять. ITA приложения включают в себя много кода на С и С++, так же как и на Lisp, следовательно, они опираются на свой опыт.

Смею предположить, что эти численные показатели даже не являются постоянными. Думаю, они возрастают, когда вы сталкиваетесь с проблемами посерьезнее, а также когда у вас работают более умные программисты. Из лучших инструментов настроящий специалист может выжать больше.

В крайнем случае, если бы вы конкурировали с ITA и решили бы писать программы на С, они бы смогли разработать ПО в 20 раз быстрее вас. Если бы вы потратили целый год на новую функцию, они бы умудрились продублировать ее менее чем за три недели. Тогда как если бы они провели всего три месяца за разработкой чего-то нового, то прошло бы пять лет, прежде чем вам бы удалось это реализовать.

И знаете что? Это только в лучшем случае. Когда мы обсуждаем размеры кода, вы неявно допускаете, что можно писать программы, как ни странно, на менее эффективных языках программирования. Но в действительности существуют пределы возможностей программистов. Если вы пытаетесь решить сложную задачу с помощью слишком низкоуровневого языка, то вы достигнете той точки, когда возникнет чрезвычайно много вещей, которые нужно одновременно удерживать в голове.

Поэтому, когда я говорю, что вооброжаемому конкуренту ITA потребовалось бы пять лет на дублирование того, что в самой ITA могли бы написать на Lisp за три недели, я имею в виду такие пять лет, когда все идет по плану и без единой ошибки. На самом деле, в большинстве компаний это работает так: любой разрабатываемый проект, требующий пяти лет работы, возможно, вообще никогда не будет закончен.

Признаю, это крайний случай. Специалисты из ITA кажутся необычайно смекалистыми, а С – довольно низкоуровневым языком. Но на конкурирующем рынке, даже разница два или три к одному была бы гарантией того, что вы всегда будете позади.

Самый верный способ

Существует некоторая вероятность, что наш невежественный начальник даже не захочет и время тратить на раздумья обо всем вышеперечисленном. Как большинство из них и делает. А все потому, что, знаете ли, когда до этого дойдет дело, невежественному начальству все равно, отвесит ли люлей за это его фирма или нет, пока невозможно доказать его вину. Самый безопасный лично для него план состоит в том, чтобы держаться поближе к центру стада.

В крупных организациях, чтобы описать такой подход, используют фразу «лучшие отраслевые практики». Ее целью является оградить невежественного начальника от ответственности: если он что-то решил в соответствии с «лучшими отраслевыми практиками», а фирма понесла убытки, то его нельзя в этом винить. Не он принял такое решение, а отрасль.

Я считаю, что данное понятие было изначально придумано для описания бухгалтерского учета и т.п. Грубо говоря, его смысл в том, чтобы не делать ничего странного. А для бухгалтерии, вероятно, это очень даже хорошая идея. Термины «передовой» и «бухгалтерия» не очень хорошо смотрятся вместе. Но когда вы вносите данный критерий в решения относительно используемых технологий, вы начинаете получать неверные ответы.

Технологии часто должны быть передовыми. В языках программирования, как указал Эранн Гет (Erann Gat), то, что на самом деле дают вам «лучшие отраслевые практики» не является наилучшим, а всего лишь относится к среднему уровню. Когда решение вынуждает вас разрабатывать ПО в темпе более агрессивных конкурентов, то «лучшие практики» это ошибочное использование термина.

Итак, перед нами два факта, которые я считаю очень ценными. На самом деле, я знаю это из своего собственного опыта. Первый факт: языки программирования различны по своим возможностям. Второй факт: большинство менеджеров намеренно это игнорируют. Эти сведения в буквальном смысле рассматриваются как самый верный способ зарабатывания денег. Организация ITA – пример этого способа в действии. Если вы хотите одержать победу в сфере разработки ПО, просто сформулируйте самую сложную проблему, какую только можете, используйте самый мощный язык программирования, который только найдете, и ждите, когда невежественное начальство ваших конкурентов приведет свои фирмы к тому, что те упадут в цене.

Приложение: мощь языков программирования

Чтобы проиллюстрировать то, что я подразумеваю под сравнительной мощью языков программирования, представьте следующую задачу. Мы хотим написать функцию, генерирующую накапливающие элементы – функцию, которая берет число n и возвращает функцию, которая использует другое число i для возврата n, увеличенного на i. (Это приращение с шагом, а не просто сложение. Накапливающий параметр должен накапливать).

В Common Lisp это будет
(defun foo (n)
  (lambda (i) (incf n i)))

а в Perl 5
sub foo {  
  my ($n) = @_;
  sub {$n += shift}
}


что включает больше элементов, чем в версии на Lisp, т.к. в Perl вам придется извлекать параметры вручную.

В Smalltalk код немного длиннее, чем на Lisp
foo: n                              
  |s|                      
  s := n.                          
  ^[:i| s := s+i. ] 

потому что, хоть, в общем и целом, лексические переменные работают, к параметру нельзя применять присваивание, поэтому и приходится создавать новую переменную s.

В Javascript пример, опять же, немного длиннее, т.к. в Javascript сохраняется различие между операторами и выражениями, поэтому для возврата значений нужен явный оператор return:
function foo(n) { 
  return function (i) { 
           return n += i } }

(если честно, Perl также сохраняет это различие, но обрабатывает это в типичной для Perl манере, позволяя опускать операторы возврата return).

Если попытаться перевести Lisp/Perl/Smalltalk/Javascript код на Python, то вы столкнетесь с некоторыми ограничениями. Т.к. Python не поддерживает полностью лексические переменные, вам придется создавать структуру данных для хранения значения n. И, хоть в Python и есть функциональный тип данных, точного представления для него нет (если тело функции не представляет из себя одно единственное выражение), поэтому вам нужно создать именованную функцию для возрвата. Вот к чему вы, в итоге, придете:
def foo(n):
  s = [n]
  def bar(i):
    s[0] += i
    return s[0] 
  return bar

Пользователи Python могли бы вполне обоснованно задать вопрос: почему они не могут просто написать
def foo(n):
  return lambda i: return n += i

или даже
def foo(n):
  lambda i: n += i

И я предполагаю, что так когда-нибудь и случится. (Но если они не захотят ждать, пока Python эволюционирует в Lisp, они всегда могут просто...)

В объектно-ориентированных языках, можно, до определенной степени, сымулировать замыкание (функцию, которая ссылается на переменные, определенные вне тела этой функции) посредством определения класса с одним методом и полем для замены каждой переменной из объемлющего контекста. Это вынуждает программиста проводить некоторого рода анализ кода, который мог бы быть сделан компилятором в языке с полной поддержкой контекста лексического анализа. И это не сработает, если к одной и той же переменной идет обращение из более чем одной функции, но этого достаточно в простых ситуациях, похожих на эту.

Эксперты по Python, похоже, согласны с тем, что для решения задач на Python предпочтительнее способ, при котором нужно писать либо
def foo(n):
  class acc:
    def __init__(self, s):
        self.s = s
    def inc(self, i):
        self.s += i
        return self.s
  return acc(n).inc

либо
class foo:
  def __init__(self, n):
      self.n = n
  def __call__(self, i):
      self.n += i
      return self.n

Я привожу эти примеры потому, что мне бы не хотелось, чтобы защитники Python говорили потом, что я исказил язык, а потому, что оба эти примера, как мне кажется, сложнее первой версии кода. Вы же делаете то же самое, когда устанавливаете отдельное место для хранения накапливающего параметра; это просто поле в объекте, вместо первого элемента списка. А использование этих специальных, зарезервированных имен полей, особенно таких как __call__, кажется немного грубоватым способом.

В соперничестве между Perl и Python заявлением со стороны Python специалистов, по-видимому, выступает то, что Python – более простая альтернатива Perl, но данная ситуация показывает, что мощь языка содержится в максимальной простоте: программа на Perl проще (меньше элементов), даже если синтаксис немного убогий.

А что насчет других языков? В других упомянутых в данном разговоре языках – Fortran, C, C++, Java и Visual Basic – неясно, можно ли на самом деле решить эту задачу. Кен Андерсон говорит, что следующий код также близок к тому, что можно получить на Java:
public interface Inttoint {
  public int call(int i);
}

public static Inttoint foo(final int n) {
  return new Inttoint() {
    int s = n;
    public int call(int i) {
    s = s + i;
    return s;
    }};
}

Этот код не соответствует нашему описанию задачи, т.к. он работает только с целыми числами. После обмена множеством электронных писем с Java специалистами, я бы сказал, что факт создания полностью полиморфной версии кода, которая работает также как и предыдущие примеры, находится где-то между чертовски сложным и невозможным. Если кому-то захочется написать такой код, мне было бы очень любопытно на него посмотреть, но лично я взял перерыв в этом деле.

Конечно, то, что вы не можете решить эту проблему на других языках программирования, не является истиной в буквальном смысле. Тот факт, что все эти языки эквивалентны по Тьюрингу означает, что, строго говоря, можно написать любую программу на любом из них. Так как бы вы это сделали? В этом ограниченном случае посредством написания Lisp интерпретатора на менее мощном языке.

Это похоже на шутку, но это так часто происходит в той или иной степени в крупных программных проектах, что данный феномен назвали десятым правилом Гринспена: любая достаточно сложная программа на С или Fortran содержит заново написанную, неспецифицированную, глючную и медленную реализацию половины языка Common Lisp.

Если вы попытаетесь решать сложную задачу, вопрос будет состоять не в том, будете ли вы использовать достаточно мощный язык, а в том, (а) будете ли вы использовать мощный язык, (б) напишете ли реальный интерпретатор для одного такого, или (в) станете ли сами компилятором в человеческом обличии для одного из таких языков. Мы уже видим, как это начинает происходить на примере для Python, где мы имитируем код, который сгенерировал бы сам компилятор для реализации лексической переменной.

Такая практика не только является общей тенденцией, но и превратилась в институционализированную деятельность. Например, в ОО мире вы много слышите о «шаблонах». Интересно, не являются ли эти шаблоны воплощением случая (в), о человеке-компиляторе, в действии. Когда я в своих программах вижу шаблоны, я рассматриваю это как сигнал тревоги. Модель программы должна отражать только ту проблему, которую ей нужно решить. Любая другая закономерность в коде, по крайней мере для меня, является признаком того, что я использую не достаточно мощные абстракции, а часто и то, что я вручную порождаю детализацию некоторого макроса, который мне нужно написать.

Замечания

— Процессор IBM 704 был размером с холодильник, но намного тяжелее. Процессор весил 3150 фунтов (около 1428 кг – прим. пер.), а ОЗУ объемом 4K располагалось в отдельной коробке, которая дополнительно весила еще 4000 фунтов (около 1814 кг – прим. пер.). Один из крупнейших холодильников для домашнего использования Sub-Zero 690 весил 656 фунтов (около 298 кг – прим. пер.).

— Стив Рассел также написал первую (цифровую) компьютерную игру Spacewar в 1962 году.

— Если вы хотите обмануть невежественного начальника так, чтобы он вам позволил писать ПО на Lisp, то можно попробовать сказать ему, что это всего лишь XML.

— Ниже представлен генератор сумм на других диалектах Lisp:

Scheme: (define (foo n)
(lambda (i) (set! n (+ n i)) n))
Goo: (df foo (n) (op incf n _)))
Arc: (def foo (n) [++ n _])

— Печальный рассказ Эрана Гата о «лучших отраслевых практиках» на JPL вдохновил меня на использование этой неверно используемой повсеместно фразы.

— Питер Норвиг выяснил, что 16 из 23 шаблонов в Шаблонах проектирования были «скрыты или проще» в реализации на Lisp.

Выражаю благодарность многим людям, которые ответили на мои вопросы о различных языках программирования и/или прочли наброски этой статьи, включая Кена Андерсона (Ken Anderson), Тревора Блеквелла (Trevor Blackwell), Эранна Гета (Erann Gat), Дена Гиффина (Dan Giffin), Сару Харлин (Sarah Harlin), Джереми Хилтона (Jeremy Hylton), Роберта Морриса (Robert Morris), Питера Норвига (Peter Norvig), Гая Стила (Guy Steele), и Антона ван Штратена (Anton van Straaten). Никто из них не несет ответственности за выраженные здесь мнения.

Дополнительно:

Многие люди откликнулись на эту беседу, поэтому я открыл дополнительную страницу, чтобы иметь возможность рассматривать сложности, с которыми они столкнулись: Re: Revenge of the Nerds.

Эта статья также повлекла за собой всесторонние и часто полезные обсуждения в списке адресатов LL1. См. в частности письмо Антона ванн Штратена по семантическому сжатию.

Некоторые из писем на LL1 побудили меня глубже копнуть в области способностей языков программирования в Succinctness is Power.

Расширенный набор канонических реализаций эталона генератора накопительного параметра представлен на отдельной странице.

Все статьи Грэма на русском тут.
Автор: @MagisterLudi
Edison
рейтинг 298,44
Изобретаем успех: софт и стартапы

Комментарии (1)

  • 0
    оффтоп, сорри, но имя автора книги мне, футбольному болельщику, показалось очень знакомым:
    — Грэм, Пол (https://ru.wikipedia.org/wiki/%D0%93%D1%80%D1%8D%D0%BC,_%D0%9F%D0%BE%D0%BB")
    — Полл, Грэм (https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%BB%D0%BB,_%D0%93%D1%80%D1%8D%D0%BC)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка