четверг, 25 июня 2015 г.

[Перевод] G. Weinberg - Что делает программу хорошей?


Итак, если мы собираемся рассматривать программирование как человеческую деятельность, то нам нужно разработать какие-то критерии для её оценивания. Это то, что мы будем подразумевать, заявляя, что один программист лучше другого, или что «эта» программа лучше «той». Хоть у каждого из нас и есть своё мнение на этот счёт, мы увидим, что ответы на эти вопросы не столь просты и очевидны, как может показаться на первый взгляд. Потому как программирование — это не просто человеческая деятельность: это сложная человеческая деятельность.
Давайте снова вспомним наш маленький пример из первой главы.

Пример 1.
XXX: PROCEDURE OPTIONS(MAIN);
     DECLARE B(1000) FIXED (7,2),
             C FIXED (11,2),
             I,J FIXED BINARY;
     C = 0;
     DO I = 1 TO 10;
         GET LIST((B(J) DO J = 1 TO 1000));
         DO J = 1 TO 1000
             C = C + B(J);
             END;
         END;
     PUT LIST('SUM IS', C);
     END XXX;

Проанализировав этот кусок кода более подробно мы увидим, что лишь небольшая его часть (около 20 %) сосредоточена непосредственно на решении задачи. Другие 80 % можно почти полностью выбросить (если не «физически», то хотя бы в воображении). На самом деле, имеющаяся проблема ограниченного дискового пространства может быть решена с помощью подходящей виртуальной машины: то есть, даже физические ограничения ЭВМ потенциально можно преодолеть с помощью грамотного психологического подхода к проектированию.
Приведенный пример, несмотря на свой небольшой размер, довольно типичен в своем отношении полезного кода, непосредственно решающего задачу, ко всему объему написанного программного кода. Поскольку люди обычно не читают программ, то немногие знают, насколько часто встречается эта ситуация. А встречается она очень часто, и это даёт нам широкое поле для действий по улучшению нашей деятельности как программистов. И всё же, в вопросе о хорошем программировании есть кое-что еще, помимо измерения количества ненужного, или, по крайней мере, вспомогательного кода. На самом деле мы не можем оценить программу саму по себе и заключить из этого, хорошая она или плохая.
Многие программисты подсознательно понимают, что есть такая вещь, как «хорошее программирование» (good programming). Хотя это подсознательное чувство может ощущаться вполне реально, из него никак не удаётся вывести такое понятие, как хорошая программа: по крайней мере, получить его в таком виде, чтобы, в отрыве от контекста, в котором она была написана и в котором она используется заключить, например, что степень её «хорошести» около 83,72 %.
Взглянем ещё раз на пример 1.Хороша ли эта программа? Возможно, она неуклюжа, но это только с одной стороны. В конце концов, мы заключили, что она неуклюжа только после того, как прочитали её код. А поскольку код обычной среднестатистической программы никто не читает, его изящность вряд ли можно принять как абсолютный показатель «хорошести» (goodness). Эффективна ли эта программа? Вряд ли мы сможем ответить на этот вопрос, не зная ничего о машине, которая её запускает, о компиляторе, и о критичности задачи, которую ей предстоит решать. Выполнится ли она в отведённое время? Какие усилия для этого придётся предпринять? Выполнит ли программа свой контракт? Мы не можем знать ответы на эти вопросы, едва взглянув на её код.
Нашему изучению психологии программирования будет препятствовать наша же неспособность измерить степень «хорошести» программы в абсолютных показателях. Но можем ли мы по крайней мере измерить её по относительной шкале? Можем ли мы сказать, что программа А лучше или хуже программы В? К сожалению, в общем случае мы даже этого не можем сделать по нескольким причинам. Во-первых, а существует ли ещё одна подобная программа для сравнения? А если она существует, как например, альтернативный компилятор ФОРТРАНа, как мы сможем договориться об относительной важности тех или иных её особенностей? Будет ли компилятор, который быстро компилирует заведомо медленный код лучше того, который медленно компилирует заведомо быстрый код?
И всё таки, бывают ли случаи, когда мы можем ввести относительную шкалу? Разве компьютер, который быстро компилирует заведомо быстрый код не лучше того, который долго и мучительно компилирует заведомо медленный код? Стоит признать, что иногда такие сравнения уместны, хотя не следует торопиться их делать. Существуют и другие метрики производительности компиляторов, помимо скорости компиляции. Это и наличие диагностики, и покрытая область языка, и надёжность объектного кода, и мониторинг времени запуска и многие другие. В очень редких случаях мы сможем найти две программы, которые будут настолько схожи, чтобы их можно было бы сравнить прямо по пунктам и найти лучшую по сумме баллов.
Гораздо чаще нам придётся оценивать программы не по отношению к другим, а по отношению к задаче, для решения которой они были написаны. Беспристрастно рассмотрев условия задачи, мы никогда не будем искать самую лучшую программу для её решения, редко будем искать хорошую, но всегда будем искать ту, которая удовлетворяет требованиям задачи.

Требования.

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

Однажды одного программиста пригласили на работу в Детройт для помощи в отладке одной большой программы, которая определяла потребность в деталях для производства ряда автомобилей. Входными данными для программы был набор перфокарт, каждая из которых представляла собой заказ на покупку автомобиля, а отверстия в ней кодировали информацию о желаемых характеристиках конкретного автомобиля от заказчика. Программа превращала перечисленные заказчиком требования в детали, которые нужно было изготовить для создания такого автомобиля. Например, выбор обивки для заднего сидения определялся такими факторами как цвет обивки, стиль пошива, качество и класс материала для обивки и наличие кондиционера в салоне автомобиля. Последняя опция «кондиционер» является хорошим примером того, насколько сложной была задача программы: хотя со стороны и кажется, что между наличием кондиционера в салоне и выбором обивки для заднего сидения нет никакой связи, но на самом деле его присутствие требует достаточно много места для установки воздуховодов. В общем случае, каждая опция может иметь влияние на выбор тех или иных деталей для производства, поэтому определение требований к ним была превосходной задачей для вычисления на компьютере.
К сожалению, когда приглашённый программист появился, основной подход к решению этой задачи уже был утверждён и использован — и он был, мягко говоря, неудачным. Каждая опция, зависящая от того или иного выбора заказчика, была выделена в отдельную ветвь программы. Получилось огромное ветвистое дерево условий с более чем пятью тысячами ветвей, изображающих решения, которые сводятся к выбору той или иной детали. Представленную в таком виде (с 16-ю программистами, параллельно корпевшими над ней) программу было совершенно невозможно отлаживать, так как приходилось тестировать каждый сценарий отдельно. Для этого данные с каждой перфокарты приходилось загружать в ЭВМ и наблюдать, как поведёт себя программа в том или ином случае. Когда прибыл наш друг, дела шли настолько плохо, что входные карты, в которых была описана вполне обычная для автомобиля конфигурация, в ходе работы программы порождали на свет монстрообразные железяки с восемью колёсами, без двигателя и тремя наборами обивки для сидений. Короче говоря, это был ад.
Как и обычно бывает с кошмарными программами, никто её таковой не считал. Наоборот, целая команда вышла работать в двойную смену, чтобы найти и уничтожить все баги, попутно добирая программистов со всей страны, включая нашего героя. Естественно, это привело к ещё большей путанице, и наш программист, после нескольких дней работы в таком режиме, понял, что дело безнадёжно — нет ни единой причины пахать здесь день и ночь, вдали от своего дома и своей семьи. Его, конечно, резко осудили за такое упадническое отношение, но всё же позволили оставить проект.
Оказавшись в самолёте, он, наконец, получил время спокойно всё обдумать и немедленно обнаружил ошибку в использованном подходе к решению задачи. К нему пришло осознание, что гораздо более удачным подходом будет разделить работу на две части. В главной программе будет использован простой цикл по множеству специально разработанных конструкторских таблиц, тем самым все решения можно сделать в рамках одной ветки, которая повторно применяется ко всем частям таблицы. Таким образом, программа по крайней мере будет гарантированно генерировать верное число колёс, двигателей и так далее. Сами таблицы будут собираться из входных данных, написанных в форме инженерных спецификаций. Это позволит самим инженерам, а не программистам, проверять их корректность, а также заменять одну спецификацию на другую без изменения всех остальных ниже по дереву решений.
К тому времени, как он сошёл с трапа самолёта, он успел записать код для обеих программ. Потребовался один день, чтобы проверить их корректность и ещё два дня на работу с местными заводскими инженерами для составления этих самых входных карточек со спецификациями. После недели тестирования на заводе он уже собирался лететь обратно в Детройт, как вдруг получил телеграмму о закрытии проекта по причине невозможности его реализации.
После телефонного звонка и спешного перелёта, наш герой вернулся в Детройт со своей версией программы. Продемонстрировав её руководству, он смог убедить их, что проект может быть реализуем, и потому попросил собрать снова команду программистов для презентации своей версии.
Естественно, другие программисты встретили его достаточно холодно (это феномен, к которому мы ещё вернёмся), но во время презентации все сидели тихо и слушали. Даже в конце презентации, вопросов не было совсем — до тех пор, пока проектировщик и создатель предыдущей версии программы не поднял руку.
«И сколько времени требуется вашей программе для решения задачи?» — спросил он, явно подчеркивая слово вашей.
«Зависит от входных данных,» — ответил программист, — «но в среднем где-то 10 секунд на карту»
«Ага.» — триумфально подметил оппонент. «Но моей программе для этого потребуется всего одна секунда на карту!».
Все собравшиеся, которые так или иначе внесли свой вклад в старую и «быструю» версию программы, вздохнули с облегчением. Но наш герой, который был моложе и понаивнее, совсем не смутился этим замечанием. Вместо этого он спокойно подметил: «Но ваша программа не работает. Если программа не должна работать, то я могу написать такую, которой потребуется одна миллисекунда на карту, что вообще больше скорости нашего считывающего устройства».

Это точное замечание, не добавившее, однако, друзей нашему программисту, содержит одну фундаментальную истину, на которую должно опираться написание любых программ. Если программа не работает, то все оценки её эффективности, адаптируемости или стоимости не имеют никакого значения. Однако, нужно понимать, что безупречная программа вряд ли когда-либо была написана вообще. Любая действительно большая и сложная программа имеет «ещё один небольшой баг». Таким образом, существуют различные степени соответствия результата работы программы её требованиям, поэтому при оценке программ следует учитывать различные типы её «неидеальности».
Для любого компилятора, например, существуют по меньшей мере несколько «патологических» случаев, которые он не может скомпилировать корректно. Что означает «патологический» зависит от нескольких точек зрения. Если это происходит с вашей программой, вы вряд ли станете рассматривать этот случай как патологический для компилятора, ведь у многих тысяч других пользователей этот баг вообще никогда не возникал и всё ок. Разработчик компилятора, однако, должен оценивать его ошибки исходя из числа пользователей, которые налетели на этот баг и стоимости ущерба, который он причинил. Это не всегда делается научным методом. Наоборот, наиболее часто обращают внимание на самого «громкого» и назойливого пользователя, или того, кто жалуется напрямую начальству разработчика. Но какой бы компилятор не был выбран, баги неизбежно появляются. И этот факт огорчает пользователей, столкнувшихся с ними, ведь они пользуются абсолютно тем же продуктом, что и тысячи других, которых, однако, он полностью устраивает.
В результате, мы явно видим разницу между программой, которая была написана для одного пользователя и продуктом, которым пользуется много людей. Когда пользователей много, то и требований к программе становится больше. Когда число требований растёт, то появляется множество определений того, что понимать под работающей программой. В наших дискуссиях о практиках программирования, мы собираемся принять во внимание разницу между программой, написанной для одного пользователя и программой, которым пользуется много людей. Они должны оцениваться по-разному, и эти оценки должны производиться разными методами.

Планирование

Даже после того, как мы оставили позади вопрос о соответствии результата работы программы её требованиям, вопрос об эффективности всё ещё не выходит на первый план. Одна из постоянно повторяющихся проблем программирования — соответствие плану, ведь программа, которая была создана слишком поздно попросту бесполезна. По крайней мере, мы должны сопоставить убытки от неимения программы в срок с потенциальной экономией, которые может принести более эффективная программа. Был один примечательный случай, когда заказчик программы прикинул, что линейно написанный программный код сохранит его нефтеперерабатывающей компании больше миллиона долларов в месяц. Однако, задержка в разработке этой программы длиной в один месяц приведёт к потере такой суммы денег, которую не перекроет даже её последующая бесперерывная десятилетняя работа!
Убытки от поздней поставки программного обеспечения не всегда настолько серьёзны, но даже если они незначительны, то скорее всего (по крайней мере в США) они повлекут за собой кучу неприятностей. На самом деле, среднестатистический менеджер предпочтёт, чтобы проект был запланирован на год и за этот же срок выполнился, нежели был запланирован на полгода, а выполнился за 9 месяцев. Это та область, в которой возможны некоторые успешные психологические исследования. Однако из других источников существуют основания полагать, что людей раздражает не столько время ожидания чего-либо, сколько отклонение этого времени от привычных средних норм. Так, например, большинство людей предпочтут каждое утро ждать автобус до работы фиксированные 10 минут, нежели 4 дня в неделю ждать его по одной минуте, а один раз в неделю прождать его 26 минут. Хотя среднее время ожидания во втором случае всего 6 минут, напряжение, вызванное длительным и внезапным ожиданием сильнее, чем сокращение средней длительности ежедневного ожидания автобуса при втором варианте.
Если это наблюдение справедливо и для программирования, то все попытки изучения эффективности конкретной практики программирования должны учитывать отклонения времени разработки, а не просто среднюю её продолжительность, как это обычно делается. Следует учитывать влияние этого фактора во всех последующих обсуждениях.

Адаптируемость

Разобравшись с предыдущими вопросами, мы можем предположить, что вот, наконец, мы добрались до вопроса эффективности как меры «хорошести» программы. Не умаляя достоинств названных факторов будет справедливо вначале разобраться также с фактором адаптируемости программ. Несомненно, существуют такие программы, которые используются один раз и затем выбрасываются на помойку. Не секрет также и то, что ещё большее количество программ выбрасывается вообще до своего первого использования. Тем не менее, большинство программ, особенно написанных профессиональными программистами, существуют и функционируют в течение какого-то определенного отрезка времени. И во время своей «жизни» большинство из них подвергается изменениям.
Мало кто из программистов, независимо от опыта, станет спорить с тем, что большинство программ изменяется в течение всего времени их использования. Почему же в том случае, когда нам нужно внести изменения в ту или иную программу, мы зачастую воспринимаем это как адский труд, и скорее соглашаемся выбросить всё и переписать заново? Чтение программ даёт нам некоторое понимание этого, так как мы редко можем отыскать программу, которая писалась заведомо с заделом на нужные изменения. Но это лишь симптом, а не заболевание. Почему программисты, которые прекрасно знают о том, что их программы рано или поздно неизбежно будут изменены, пишут их таким образом, будто вовсе не думают об этом? Почему вместо этого все их творения порой выглядят так, словно они специально были дьявольски хитро защищены от изменений, словно гробницы фараонов от расхитителей?
Ответы на эти вопросы есть и они лежат в направлении наших психологических исследований. Но на данном этапе наша задача не отвечать на них, а просто поднять их и осознать их значимость в наших дискуссиях о том, что делает программы хорошими. В связи с этим возникает ещё один вопрос, касающийся, конечно, программной документации: для чего мы пишем документацию к программе, если не для того, чтобы её было проще модифицировать? Согласимся, что качество документации и простота изменений (и запланированных, и незапланированных), должны быть учтены в любой программе или приняты во внимание программистом, который пишет эту программу.
Стоит сказать ещё пару слов, прежде чем мы перейдём к вопросу эффективности. Адаптируемость стоит денег. Случается, конечно, что мы всё же получаем программу, которая и адаптируема, и устраивает нас как заказчиков во всех других отношениях. Но в общем случае мы платим за то, что мы получаем — а получаем мы зачастую не то, за что заплатили.
В своих математических работах по генетике, Р.А. Фишер вывел закон, который часто называют Основным Законом Фишера — очень говорящее название, учитывая важность многих других его теорем. Основная Теорема (или Закон) Фишера гласит, что система, наилучшим образом адаптированная к конкретной окружающей среде, обладает худшей адаптивностью по отношению к другим средам. Немного расширив наше воображение, мы увидим, что этот закон может быть также хорошо применим для компьютерных программ, как и для улиток, мушек-дрозофил или черепах.
Чтобы программа была эффективной, она должна воспользоваться особенностями задачи, которую призвана решать, а также особенностями платформы, на которой она будет запущена. Программы, которые игнорируют особенности машины, работают с риском понести серьёзные «штрафы» по производительности, а программисты, которые умеют использовать особенности решаемой проблемы — это как раз те люди, которые могут всё это дело ускорять, оптимизировать и уменьшать в размерах.
Один пример, не очень редкий для такого типа программирования — это написание ассемблерных вставок, которая требует владения специальной техникой отслеживания таких мест в коде, которые можно заменить ассемблером. Один программист однажды заметил, что в программе присутствует определенное число, которое можно использовать как целочисленный множитель для кода операции. Такое использование позволяло практически всегда получать на выходе непустой список неповторяющихся адресов из допустимого диапазона. Таким образом, используя этот множитель, программисту удавалось получать компактное и сравнительно быстрое преобразование из символьного кода в машинный язык.
Программист был щедро вознагражден за свою гениальность — его пример приводили нескольким поколениям более юных программистов как образец той работы, к которой они должны стремиться. К сожалению, вскоре, написанный им кусок кода приказал долго жить, потому что, как это обычно и случается, набор допустимых кодов операций для ЭВМ был расширен. Когда эта небольшая порция новых кодов была добавлена, старый множитель потерял все свои магические свойства компактности и уникальности. Как оказалось впоследствии, для нового расширенного набора кодов такой множитель вообще невозможно было отыскать.
Результатом попыток его поиска стал отказ от этого хитрого приёма в пользу поиска по более общим хеш-таблицам, которые могли содержать дубликаты. К несчастью, старый приём была заброшен лишь после большого количества попыток применить его к изменившимся условиям. Но это было лишь потерей времени. В ретроспективе, экономия времени от его использования за всё время многократно превысилась потерей времени от попыток применить его к новому набору кодов операций. Если бы более общий метод с использованием хеш-таблиц был задействован сразу же (вместо старого приёма, хоть он и обладал большей эффективностью), то (и в этом «то» и заключается наш пример) изменений было бы минимум и это сэкономило бы уйму времени. И действительно, вся модификация метода состояла бы только в добавлении новых кодов операций в определенное место таблицы.
Можно привести ещё сотни подобных примеров, но суть у них одна. Когда мы требуем от программы эффективности, мы тем самым зачастую требуем «жёсткий» код, который будет сложно изменить. В терминах высокоуровневого программирования, мы часто спускаемся на уровень машинного языка, чтобы сделать код более эффективным. Тем самым мы жертвуем по меньшей мере одним из преимуществ высокоуровневой программы — способностью к переносимости между машинами и, в действительности, привязываемся к конкретной машине или реализации.
В самом деле, менеджеры, активно ратующие за эффективность — это те самые люди, которые будут рвать на себе волосы, когда узнают о цене изменения написанной программы. В то же время менеджеры, которые требуют обобщенных и легко модифицируемых программ не станут жаловаться, когда обнаружат, насколько медленными и большими оказываются эти программы. Нам нужно трезво оценивать такие вещи: ни психология, ни магия не поможет нам достичь одновременно двух взаимоисключающих целей. Требование эффективности и адаптируемости в одной программе — это как желание жениться на красавице и скромняжке в одном лице. Хотя скромность и красота бывает сочетаются в одной женщине, нам, вероятнее всего, придется выбирать. В конце концов, это лучше, чем вообще ничего.

Эффективность

Оценка настоящей эффективности программы — это не настолько простая задача, как может показаться на первый взгляд. Во-первых, эффективность не всегда можно измерить в терминах времени запуска, которое обычно представляют как разницу между временем до и после запуска программы. Например, широко используемый критерий «скорость компилятора» — это число перфокарт, которые он сможет обработать за единицу времени (минуту). Если мы попытаемся использовать этот критерий для сравнения скорости выполнения инструкций на ассемблере с компилятором, скажем, ФОРТРАНа, то ассемблерная программа, скорее всего, окажется более эффективной, так как каждая перфокарта со входными данными для неё будет содержать меньше информации. Иногда компиляторы сравнивают на основе числа машинных команд в единицу времени (минуту), но такой критерий заставляет нас давать фору компилятору просто потому, что он многословен при переводе в объектный код.
Компилятор — это, конечно, особый «зверь», поскольку в его случае мы должны измерять эффективность произведенного кода вместе с эффективностью самого процесса компиляции. Но всё же, поскольку эффективность компиляторов — это тема многих жарких дискуссий, будет полезно привести пример, иллюстрирующий насколько просто мы можем запутаться в вопросах эффективности. Представьте, например, сравнение двух компиляторов одного языка с помощью критерия «количество перфокарт в минуту». Первая ловушка здесь лежит в части «компиляторы одного языка». Очень малое число компиляторов обрабатывают в точности одни и те же области одной версии какого-либо языка. Поэтому, если существует какая-то разница между двумя компиляторами, мы будем вынуждены отрегулировать эффективность, измеренную с помощью выбранного критерия для подсчёта разницы в полученных значениях этих двух версий.
Эффект этой «небольшой разницы» в покрытии исходного языка разными компиляторами может быть просто поразительным. Зачастую, если разработчик компилятора не покрывает процентов 10 возможностей языка, то ему удаётся создать компилятор, который работает на 50 процентов быстрее. К сожалению, какие именно 10 процентов языка не вошли зависит от машины и способа компилирования, так что проектировщики языка попросту не могут ответить, какие подмножества их языка можно скомпилируются эффективнее.
Улучшения в эффективности возможны благодаря ослаблению требований в других областях. Действительно, если мы в конкретной программе больше всего волнуемся за эффективность, то первым делом необходимо всегда искать те области, в которых требования могут быть изменены в угоду эффективной обработки компьютером и в ущерб удобству пользователей. Разумеется, время обработки компьютером — это не единственный элемент в типичном проекте, который стоит денег. Время людей также стоит денег. Поэтому, придётся хорошенько всё взвесить, подсчитать, составить отчёт, что также займёт какое-то время. И мы должны очень рационально подойти к этому выбору, но гораздо чаще выбор делается ещё до того, как вы пойдёте показывать ваш отчёт начальству.
Сокращение требований может иметь и отрицательный эффект для времени обработки. Если, например, огромная работа по ручной обработке входных данных была оставлена в системе для облегчения вычислений компьютера, мы можем обнаружить, что мы платим гораздо больше за повторные запуски, чем экономим на эффективности. Этот эффект довольно часто можно наблюдать во время сравнения двух компиляторов, которые, несмотря на то, что они обрабатывают идентичные языки, различаются в диагностике и мониторинге запуска, которые они производят. Если мы сэкономим 20% в общем времени компиляции благодаря отключению или упрощению проверки ошибок, нам будет не лишним убедиться в том, что программисту не придётся делать на 40% больше запусков компилятора для получения того же количества диагностической информации.
Точно так же опасно вводить слишком простые критерии эффективности при запуске на одной машине, в одном окружении. Это просто детские шалости по сравнению со сложным процесом получения значимых оценок эффективности в мультипроцессорных или многопрограммных системах. Что будет лучше, занять 40 килобайт основного хранилища на час или 80 килобайт на 30 минут? Ответ на этот вопрос очень зависит от конкретного состояния машины, во время которого происходит запуск и он может меняться от одного запуска к другому. Например, в какой-то определенный день добавление 40 килобайт нагрузки на память никак не скажется на производительности машины и на выполнении других задач, в другой же день эти же 40 килобайт могут заблокировать машину на целый час.
Запуск на системах с динамической регулировкой нагрузки может сгладить некоторые колебания, возникающие с производительностью в мультипрограммных вычислениях, которые мы наблюдаем сегодня. Вполне может быть, что как и в случае с планированием в программировании, среднестатистический пользователь небольшому среднему времени запуска предпочтет небольшое среднее отклонение от него, чтобы рационально спланировать свою работу. Если это так, то все временные затрат от начала до конца работы должны быть обдуманы, распланированы и учтены, а не только «время обработки компьютером».
Отойдя немного в сторону компьютерного окружения, мы можем утверждать, что лучшие программы — это те, которые могут запускаться в различных объемах памяти и на машинах с различной конфигурацией. Такая программа не всегда будет эффективной для какой-то конкретной конфигурации (вспоминаем Основной Закон Фишера), но от неё можно ожидать более последовательные изменения производительности, потому что ей никогда не придётся простаивать без ресурсов. Машины с виртуальной памятью дают нам определенную гибкость без специальных приёмов программирования, хоть есть основания утверждать, что улучшение производительности можно получить даже в этом случае, если учесть кое-какие моменты.
Например, если нам известен размер страницы памяти нашей системы, мы можем избежать излишней её переразбивки путём формирования секций нашей программы таким образом, чтобы они входили в одну страницу памяти и не пересекали её границ, за исключением случаев логического перехода. Однако, если мы это сделаем, то программа станет менее эффективной на других платформах с другим размером страницы. Аналогично, мы можем выбрать алгоритм, которому потребуется не больше и не меньше памяти в пределах страницы, чем определено в нашей системе; но, опять же, такая программа не сможет воспользоваться преимуществами системы с избыточным размером страницы и выполнится самым неэффективным способом в системе с меньшим размером страницы памяти.
Все это говорит о том, что эффективность — это неясная и туманная проблема компьютерных вычислений. Более того, с ежегодным удешевлением стоимости вычислений и одновременным подорожанием работы программиста, мы давно прошли ту точку, в которой типичная установка программы затрачивала бо́льше ресурсов программиста, чем её непосредственная «производственная» работа. Этот дисбаланс еще более заметен, когда вся работа, ошибочно классифицируемая как «производственная», проходит под заголовком «отладка». Но, какими бы не были критерии, этот дисбаланс существует. И он растёт. И, как ожидается, с каждым годом мы будем всё меньше и меньше слышать об эффективности (efficiency), но все больше и больше о результативности (effectiveness).

Резюме

Вопрос того, что делает программу хорошей непрост, и не может иметь однозначно правильного ответа. Каждая программа может быть оценена по своим достоинствам и также по отношению к своему окружению. Вот несколько важных факторов:
1. Выполняет ли программа свой контракт? Соответствует ли она своим требованиям? Как хорошо она это делает?
2. Делает ли она свою работу в согласовании с планами и сроками и насколько большое отклонение от плана мы можем ожидать в зависимости от выбранного подхода?
3. Можно ли будет модифицировать программу в соответствии с изменившимися условиями? Сколько это будет стоить?
4. Насколько программа эффективна, и что мы подразумеваем под эффективностью? Не размениваем ли мы эффективность в одной области на неэффективность в другой?
В будущем, и особенно в дальнейших дискуссиях в этой книге, нам следует воздерживаться от использования таких выражений как «хорошая программа» или «хороший программист» в смысле некого термина, которое каждый понимает одинаково.

Вопросы

Для менеджеров
1. На каком основании вы вознаграждаете своих программистов? Не противоречат ли ваши критерии друг другу, как, например, запрос на эффективную и универсальную программу? Насколько откровенны вы со своими программистами в обозначении того, что вы ждёте от создаваемого ими продукта? Или же вы просто говорите им о том, что вы хотите получить быструю, маленькую, аккуратную, легко модифицируемую программу, лишенную ошибок за одну неделю?
2. Как программы, которые вы разрабатываете, выглядят с точки зрения адаптируемости? Дорого ли вам обходится изменение ваших программ? Если так, то очевидны ли вам те пункты ваших целей, поставленных перед программой, которые приводят к такому результату?
3. Насколько важно планирование в вашей организации? «Раз уж промахнулся, то всё равно на сколько» или вы ратуете за то, что общая согласованность лучше случайной удачи? Сталкивались ли вы со случаями, что программист выбирает не очень хорошую практику написания кода, лишь бы успеть к сроку, даже если он рискует застрять и вообще не закончить программу?

Для программистов
1. Формируете ли вы четко критерии для вашей программы перед началом проекта? Появляются ли эти критерии от вашего чувства важности того или иного качества программы, или же их формулирует вам ваш менеджер? Изменяются ли ваши критерии во время написания проекта, или же у вас есть четкая установка не менять их и постоянно на них ориентироваться?
2. Как часто вы во время написания программы думаете о том, кому придётся её модифицировать? Часто ли вы проклинали автора программы, которую пришлось модифицировать вам?
3. Пытались ли вы когда-нибудь сделать программу эффективнее в ущерб её завершению? Или «выполнить всё до дедлайна» вместо того, чтобы сделать всё правильно?

Комментарии к главе (юбилейное 25-летнее издание)

Крупнейший из новых факторов, определяющих то, как мы оцениваем качество кода — это экономический фактор. Программа, которая продаётся и приносит прибыль очевидно лучше той, которая этого не делает. По крайней мере, уже несколько человек из числа непрограммистов уверяли меня, что Билл Гейтс — это величайший в мире программист. В моей карьере программиста, большая часть написанного мной кода операционных систем, была благополучно выброшена, поэтому я не могу оценить его финансовую ценность. Но, положа руку на сердце, если цель написания программы — заработать денег, то лучшим программистом будет тот, чья программа больше этих самых денег заработает — ведь в таком случае она будет лучшей реализацией, удовлетворяющей своим требованиям.
В заглавной истории главы моей книги о противостоянии эффективности и результативности был приведен пример с чтением перфокарт, но суть его не меняется, какое бы устройство ввода не было использовано. Через множество лет, я определился с понятием, обобщающим этот случай: качество — это мера для какого-то человека. Для программистов того времени, скорость была более важной мерой, нежели соответствие требованиям. Изучение психологии оценки значимостей объясняет, почему разработчики делали именно тот выбор, который они делали — собирается пазл, который, в противном случае, стал бы просто древним мифом.
Прошедшие годы утвердили значимость временных отклонений в управлении качеством. Мы можем работать лучше в среднем, но если отклонения в запланированном времени остаются или даже возрастают, то заказчики остаются недовольными.
Десять лет назад я был смущен несколько сексистским языком, который так бессознательно использовался в семидесятых («прекрасная и скромная жена» и др.), но теперь я довольно скрупулезно перечитываю свои старые работы и искореняю эти выражения. Читая оригинальный текст книги сегодня, я увидел, что моё видение этих изменений в языке является следствием моего взросления и, к счастью, взросления всего общества в целом. Двадцать пять лет назад, мало кто из нас обращал внимание на то, насколько нелепые и дискредитирующие выражения мы использовали.
Многие менеджеры до сих пор хотят всего и сразу, и не могут понять важность поиска компромиссов в решаемых задачах для создания лучшего продукта из возможных. Компромисс между длительностью компьютерного вычисления и продолжительностью человеческой работы сместился, как и предполагалось. «И, как ожидается, с каждым годом мы будем всё меньше и меньше слышать об эффективности (efficiency), но все больше и больше о результативности (effectiveness)». Несмотря на это, до сих пор есть задачи, где эффективность важна, хотя и не вытесняет все остальное. Мы изучаем такие задачи.
Вообще говоря, я даже удивлен, насколько хорошо поставленные в конце главы вопросы выдержали проверку временем — с некоторыми незначительными исключениями. Кажется, что человеческое поведение обладает некой последовательностью, чего не скажешь о человеческих технологиях.
Читать дальше......

пятница, 19 июня 2015 г.

Книгомай: "Остров Медвежий", "Дерсу Узала", "По Уссурийскому Краю"

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

Алистер Маклин - "Остров Медвежий".


Хотелось прочесть что-нибудь о приключениях на богом забытом острове близ Гренландских берегов, о суровой северной природе, о выживании на клочке земли без продовольствия и нежности со стороны природы, о вечных снегах и студеных морях. Одна из моих любимых тем. Уж не знаю, чем меня манят отдаленные и отнюдь не теплые места нашего земного шарика, но тема прямо таки любимая. Однако, меня ждало разочарование. Вместо всего этого я получил средней паршивости детективчик, основные действия которого происходят на посудине, которое держит курс на упомянутый остров.
Главный герой словно Шерлок (из одноименной экранизации с Дауни Младшим), с невозмутимым выражением лица шныряет по кораблю в поисках улик и строя свои гипотезы относительно происходящего. И все это вызывает хоть какой-то интерес ровно до того момента, как все герои этого, прямо-таки киношного (а фильм и вправду есть), действа не высаживаются на острове Медвежий. Тут начинается какое-то сумасшествие: и золото третьего рейха, и заговор Второй Мировой и куча других тем, которые интересны, возможно, любителям какого-нибудь РЕН-ТВ. Все это скатывается в кислую бондиану, которую хочется поскорее закрыть.
Красной нитью в рассказе идёт потребление виски: тут его хлещут без конца и края, и стар, и млад. Порой складывается ощущение, что все судовые запасы - это ящики с вискарём. Сразу видно, что автор произведения - шотландец. В целом, произведение мне не понравилось: возникло ощущение, что трачу драгоценное время на пустую книгу, но с новой и неклассической художественной литературой это не редкость. Не думаю, что в ближайшее время вернусь к этому автору. Детективы - не моё совершенно, хотя в библиографии Маклина есть множество таких произведений, по которым сняты фильмы. Впрочем, и действие в романе "Остров Медвежий" было больше похоже на какой-то зарубежный фильм с картонными героями и шаблонной крутизной протагониста.

Владимир Арсеньев - "Дерсу Узала".

После того, как я "повёлся на название" книги Маклина, решил реабилитироваться и почитать настоящие путевые заметки. Ещё в детстве, в журнале "Сибирячок" читал я отрывок из "Дерсу Узала" и точно знал, что в книге повествуется о походах в тайгу Уссурийского края. Недолго думая, её и взял, и не прогадал - книга зашла очень хорошо и оказалось тем, что я искал. Вряд ли можно сказать, что она обладает выдающейся литературной ценностью. Также наверняка её нельзя однозначно причислить к художественной литературе: это такая смесь из дневниковых записей и художественного описания реального маршрута экспедиции по освоению Уссурийского края в 1902 году. Дерсу Узала, как главный герой книги, был проводником автора в той экспедиции и его близким другом.

В.К. Арсеньев и Дерсу во время экспедиции, фото В.К. Арсеньева


Будучи инородцем (в те времена этим словом называли все малые народности нашей необъятной и оно не носило ещё негативного оттенка) и прожив всю жизнь в тайге, Дерсу на протяжении всего романа проявляет свои лучшие качества человека, выросшего в суровом краю, где принято помогать не только ближнему, но и случайному путнику, бережно относится к природе, находить применение всему и не выбрасывать лишнее. То и дело автор удивляется тому, насколько Дерсу, несмотря на свою простоту, наивность, некультурность и необразованность, веру в одушевленность всего окружающего мира - человечнее, добрее и мудрее многих европейцев; насколько внимателен к деталям и мелочам он, выросший в тайге и читающий окружающий мир как открытую книгу. Читая роман, проникаешься уважением к этому человеку природы, к его мировоззрению, которое многим уже тогда казалось смешным и отсталым, но лишь на первый взгляд. И забавным и жалостливым кажется момент, когда Дерсу оказывается в городе:

Однажды, войдя к нему в комнату, я застал его одетым. В руках у него было ружьё.
— Стрелять, — отвечал он просто и, заметив в моих глазах удивление, стал говорить о том, что в стволе ружья накопилось много грязи. При выстреле пуля пройдёт по нарезам и очистит их; после этого канал ствола останется только протереть тряпкой.
Запрещение стрельбы в городе было для него неприятным открытием. Он повертел ружьё в руках и, вздохнув, поставил его назад в угол. Почему-то это обстоятельство особенно сильно его взволновало.
На другой день, проходя мимо комнаты Дерсу, я увидел, что дверь в неё приотворена. Случилось как-то так, что я вошёл тихо. Дерсу стоял у окна и что-то вполголоса говорил сам с собою. Замечено, что люди, которые подолгу живут одиноко в тайге, привыкают вслух выражать свои мысли.
— Дерсу! — окликнул я его.
Он обернулся. На лице его мелькнула горькая усмешка. — Ты что?. — обратился я к нему с вопросом.
— Так,—отвечал он. — Моя здесь сиди все равно утка. Как можно люди в ящике сидеть? — Он указал на потолок и стены комнаты. — Люди надо постоянно сопка ходи, стреляй.
Дерсу замолчал, повернулся к окну и опять стал смотреть на улицу. Он тосковал об утраченной свободе. «Ничего, — подумал я. — Обживётся и привыкнет к дому».
Случилось как-то раз, что в его комнате нужно было сделать небольшой ремонт: исправить печь и побелить стены. Я сказал ему, чтобы он дня на два перебрался ко мне в кабинет, а затем, когда комната будет готова, он снова в неё вернётся.
— Ничего, капитан, — сказал он мне. — Моя можно на улице спи: палатку делай, огонь клади, мешай нету. Ему казалось все так просто, и мне стоило больших трудов отговорить его от этой затеи. Он не был обижен, но был недоволен тем, что в городе много стеснений: нельзя стрелять, потому что всё это будет мешать прохожим.
Однажды Дерсу присутствовал при покупке дров; его поразило то, что я заплатил за них деньги.
— Как! — закричал он. — В лесу много дров есть, зачем напрасно деньги давай?
Он ругал подрядчика, назвав его «плохой люди», и всячески старался убедить меня, что я обманут. Я пытался было объяснить ему, что плачу деньги не столько за дрова, сколько за труд, но напрасно. Дерсу долго не мог успокоиться и в этот вечер не топил печь. На другой день, чтобы не вводить меня в расход, он сам пошёл в лес за дровами. Его задержали и составили протокол. Дерсу по-своему протестовал, шумел. Тогда его препроводили в полицейское управление. Когда мне сообщили об этом по телефону, я постарался уладить дело.
Сколько потом я ни объяснял ему, почему нельзя рубить деревьев около города, он меня так и не понял.
Случай этот произвёл на него сильное впечатление. Он понял, что в городе надо жить не так, как хочет он сам, а как этого хотят другие. Чужие люди окружали его со всех сторон и стесняли на каждом шагу. Старик начал задумываться, уединяться; он похудел, осунулся и даже как будто ещё более постарел.
Следующее маленькое событие окончательно нарушило его душевное равновесие: он увидел, что я заплатил деньги за воду.
— Как! — опять закричал он. — За воду тоже надо деньги плати? Посмотри на реку, — он указал на Амур, — воды много есть. Землю, воду, воздух бог даром давал. Как можно?
Он недоговорил, закрыл лицо руками и ушёл в свою комнату. Вечером я сидел в кабинете и что-то писал. Вдруг я услышал, что дверь тихонько скрипнула. Я обернулся: на пороге стоял Дерсу. С первого взгляда я увидел, что он хочет меня о чём-то просить. Лицо его выражало смущение и тревогу. Не успел я задать вопрос, как вдруг он опустился на колени и заговорил:
— Капитан! Пожалуйста, пусти меня в сопки. Моя совсем не могу в городе жить: дрова купи, воду тоже надо купи, дерево руби — другой люди ругается.
Я поднял его и посадил на стул.
— Куда же ты пойдёшь? — спросил я.
— Туда! — он указал рукой на синеющий вдали хребет Хехцир.
Жаль мне было с ним расставаться, но жаль было и задерживать. Пришлось уступить. Я взял с него слово, что через месяц он вернётся обратно, и тогда мы вместе поедем на Уссури. Там я хотел устроить его на житьё у знакомых мне тазов.
Я полагал, что Дерсу дня два ещё пробудет у меня, и хотел снабдить его деньгами, продовольствием и одеждой.
Но вышло иначе.
На другой день, утром, проходя мимо его комнаты, я увидел, что дверь в неё открыта. Я заглянул туда — комната была пуста.

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

Владимир Арсеньев - "По Уссурийскому Краю".


В "Дерсу Узала" знакомство с главным героем начинается с первых глав, между тем у неё есть предыстория. Эта предыстория как раз описана в книге "По Уссурийскому Краю". Хронологически, события в ней развиваются до "Дерсу Узала", но формат романа точно такой же: путевые заметки параллельно с повествованием о ходе экспедиции. Здесь Арсеньев больше внимания уделяет каким-то более общим вещам, в этом же романе происходит знакомство с Дерсу. Обе книги выходили в составе издания "В дебрях Уссурийского края" и я рекомендую читать обе.
Думаю, в современном мире, где явно сформировано общество потребления (не люблю это заезженное выражение), вовсе не лишним будет почитать о бережном и внимательном отношении ко всему: к природе, к человеку, к незнакомцу, к животным и растениям. Обитая в цивилизованных каменных джунглях, мы уже утрачиваем понимание того, что такое взаимовыручка, бескорыстность, опасность для жизни, необходимость трудиться, превозмогая адскую усталость и боль, просто для того, чтобы остаться живым. Вряд ли кто-то из нас окажется в тайге во время снежной пурги, или столкнется нос к носу с медведем или уссурийским тигром (с последним в дикой природе столкнуться уже практически невозможно), или будет вброд переходить порожистую реку с несущимися брёвнами, или подыматься в каменистые сопки 4 дня без еды, но мы не должны забывать, что не всё во власти современного Homo Sapiens. Но в силах каждого отдельного человека быть честным, помогать слабому и не терять собранности и мужества. Этому и учат книги Арсеньева.

P.S. По книге "Дерсу Узала" в 1975 году режиссёром Акирой Куросавой был снят одноимённый советско-японский фильм, получивший в 1976 году премию "Оскар" в категории "лучший иностранный фильм, показанный в США".
Читать дальше......