Написание кода

Написание кода

Рано или поздно все сводится к коду. Как бы ни хотелось избавиться от этого противного этапа, нельзя создать программное обеспечение, не написав код. Даже если вам кажется, что можно собрать приложение из готовых блоков, все равно придется написать код, который свяжет их все вместе и будет передавать результаты из одного модуля в другой.
Это и благо и несчастье одновременно. Благодаря хорошему коду наши системы становятся устойчивыми (robust) - в них не возникают отказы и производительными (high-performance) - они быстро реагируют на наши требования. Благодаря хорошему коду мы выпускаем на рынок лучшие продукты, дающие нам преимущество в конкурентной борьбе. Откуда же берется тот ужасный код, который навязывают потребителям? Пусть тот, кто думает, что «код - он и в Африке код», сравнит материалы, написанные для «USA Today» и для «Economist». Те и другие написаны в общем-то на одном языке, но отличия в уровне, качестве и стиле очевидны. Сказать, что «английский - он и в Африке английский», значит не понимать существующих между ними различий.
Как еще говорят профессионалы, «дрянь можно написать на любом языке». Это ставит менеджеров программных разработок перед проблемой. В большинстве своем они сами когда-то писали код и, наверное, неплохой: часто благодаря этому они продвигаются по служебной лестнице. Но вполне вероятно, что они работали с другим языком программирования, а не с тем, который принят в их текущем проекте. В результате возникает то, что можно назвать разобщением первого уровня: несмотря на то,.
что менеджер разбирается в общих вопросах и задачах программирования, он может не вполне понимать, какие новые и интересные возможности открывает выбранный модный язык.
Но эта проблема несравнима по сложности с той, которая возникает у менеджера программного проекта в общении со своим менеджером, который наверняка не написал в своей жизни ни строчки кода. Объяснять ему, что проект затягивается из-за бага в коде - это все равно, что сказать, что его машину нужно отправить в ремонт, потому что в ней сломался кирдык. И даже хуже того, потому что автомеханик, наверно, скажет, сколько времени нужно, чтобы починить или заменить кирдык, а мы такого прогноза, увы, дать не можем. Когда в программе ломается кирдык, это может быть надолго.
Признаюсь, что у меня нет решения для второй проблемы. Я могу лишь попытаться изложить ситуацию с предельной ясностью. Зато по поводу решения первой проблемы - менеджера и «нового языка» - у меня есть некоторые рекомендации. Мое решение простое: менеджер программного проекта должен постараться познакомиться с новым языком на практике.
Как менеджеру изучить новый язык программирования.
Раз в несколько лет на сцене появляется новый язык программирования, который обещает воплотить все наши робкие мечты и превзойти все языки, созданные до него. Разработчиков поопытнее шарахаются от таких новинок, как черт от ладана. Они слишком хорошо знают, что все языки более или менее похожи, и не обращают внимания на шум, поднимаемый вокруг каждого такого новшества.
С другой стороны, всегда есть надежда: вдруг на этот раз золота окажется больше, чем мусора. Вдруг кто-то изобрел более удачный цикл «do». Дело в том, что если мы неустанно ищем возможности усилить свои позиции в конкурентной борьбе, то нельзя просто отмахнуться от такого нового предложения. Приходится стиснуть зубы и снова бросаться на штурм.
Но как распознать действительную ценность нового языка, не тратя на его изучение слишком много нашего драгоценного времени? Чтение книг по новым языкам редко оказывается вдохновляющим занятием. Честно говоря, в большинстве своем они ужасны. Иногда обнаруживаются жемчужины, которые не только порождают новое поколение программистов, но и выдерживают проверку временем; например, K&R
стала классическим руководством по программированию на C с тех пор, как этот язык приобрел популярность. Книга служит для первого знакомства с C и содержит все, что требуется от справочного руководства, в виде краткого, доступного для понимания и недорогого (когда-то) издания в бумажной обложке. (Сегодня эта тоненькая книжечка стоит $40.) Но, как я сказал, такого рода краткие, хорошо организованные и высокоинформативные книги являются редкостью.
Поэтому, посмотрев одну-другую книгу по новому языку из тех, что появились в продаже, большинство профессионалов начинает изучать новый язык с того, что пишет свою первую программу. Это более эффективный метод изучения, чем просто чтение надуманных и искусственных примеров, предлагаемых большинством книг для знакомства с языком. Чтобы освоить что-то, обычно очень полезно попытаться решить задачу.
Задача, более точная формулировка.
Если вы решили написать программу, то нужно выполнить калибровку. С момента выхода K&R стало почти стандартом, что первая программа на новом языке всего лишь печатает или выводит на экран сообщение «Hello, world». Конечно, вы узнаете, как вывести строку и воспользоваться компилятором, компоновщиком и загрузчиком, но не более того. То, что вы получите в результате, нельзя даже рассматривать как первый проект.
Я считаю, что первая программа не должна быть такой тривиальной. Нет риска - нет и выигрыша. С другой стороны, нежелательно затевать большой проект. Нужен проект, настолько сложный, чтобы для его реализации потребовалось изучить идиоматику нового языка, но не пришлось осваивать новую предметную область. Это значит, что нужна некая стандартная задача, так чтобы при каждой реализации ее решения средствами нового языка программирования, который требуется оценить, происходила ваша калибровка. Иными словами, нужно обойтись без создания новой науки и разработки новых алгоритмов. Теоретически это упражнение должно становиться более легким для каждого нового языка, поскольку проблематика оказывается для вас знакомой и можно больше времени уделить оценке того, насколько хорошо решение формулируется на новом языке. Если для решения стандартной задачи средствами нового языка требуется вчетверо больше времени, чем обычно, то могут возникнуть вопросы относительно характеристик нового языка и/или динамике его изучения.
Что должно быть в стандартной задаче?.
Хорошо, что вы задали этот вопрос. Вот ряд вещей, которые я хотел бы исследовать во всяком новом языке:.
• Как вывести строку. Это полезно уметь, например, чтобы показать пользователю приглашение для ввода данных. Как указывалось в предыдущем разделе, это как раз и демонстрирует программа Hello, world.
• Как получить данные, введенные пользователем. Можно начать с простых строк и двигаться к форматированным числам. Простое чтение символьных строк дает массу возможностей, поэтому такая задача может служить хорошей отправной точкой.
• Простые алгоритмы. Нужно поработать с какими-то данными. Не требуется ничего особенного, просто какие-нибудь присваивания, арифметические операции и т. д. При этом выяснится, потребуются ли математические библиотеки и т. п. Не обязательно проверять, выдержит ли оснастка вашего судна шторм, но спустить его на воду придется.
• Как осуществляется постоянное хранение данных. Это уже.
большой шаг вперед, потому что надо записать результат и сохранить его так, чтобы он не пропал после завершения программы. В идеале пробная программа должна и записать данные в постоянное хранилище, и считать их оттуда. Вообще говоря, это демонстрирует существующий в языке интерфейс к некой файловой системе. Достаточно ограничиться простым текстовым файлом и не усложнять задачу.
• Как реализовать «стандартную» структуру данных типа связного списка. Необходимость в них регулярно возникает при решении технических задач программирования, поэтому полезно иметь такую структуру в учебной задаче, чтобы узнать, как она реализуется в новом языке. Этот пункт просто развивает «простые алгоритмы», о которых сказано выше.
• Как обрабатывать ошибки. Что делать, если данные, введенные пользователем, или в файле не те, на которые вы рассчитывали - искажены или просто дурацкие (или вообще отсутствуют)?.
• Как оценить возможности абстрагирования и инкапсуляции. Насколько просто или сложно реагировать на изменение требований или постановку задачи?.
Я хочу сделать важное предупреждение. Здесь исследуются возможности языка для «программирования на низком уровне». Несмотря на их важность, при этом не тестируются возможности языка для программирования «высокого уровня», например, возможность иметь открытые или закрытые интерфейсы, взаимодействие с другими программными инфраструктурами и, разумеется, графика. В частности, мощь последних пополнений нашего арсенала языков в значительной мере определяется богатством библиотек классов и т. д. Их можно оценить только за счет ощутимых дополнительных трат времени. Однако исследовать их будет проще, если вы разберетесь с базовыми методами программирования.
Игра в животных.
Вот программа, которую я переписываю в качестве своей стандартной задачи с 1960-хгг.
Она называется «The Animal Game» (игра в животных).
Программа представляет собой интерактивный диалог между пользователем и программой. Пользователю предлагается «задумать животное». Затем программа начинает задавать вопросы: «Это животное - гончая собака?».
Если пользователь задумал гончую, то отвечает «да»; тогда программа хвалит себя за проницательность, благодарит пользователя за игру и заканчивает работу.
Если же пользователь задумал другое животное, он отвечает «нет». Удрученная программа сообщает: «Увы, я не угадала ваше животное. Назовите мне его и введите вопрос, на который нужно ответить „да“ для вашего животного и „нет“ для гончей».
Например, если человек задумал форель, он должен ввести «форель», а затем вопрос «это рыба?». Ответом будет «да» для форели и «нет» для гончей.
Побив программу и введя свое животное и свой вопрос, пользователь читает благодарность, а программа снова завершается.
Однако когда пользователь начинает игру в следующий раз, происходит нечто иное. После предложения «Задумайте животное» первым задаваемым вопросом становится «Это рыба?» Если ответ «да», программа спрашивает «Это форель?» Но если на вопрос «Это рыба?» ответом будет «нет», программа спрашивает: «Это гончая?» Если пользователь задумал форель или гончую, программа угадывает правильно и выигрывает. Если же пользователь задумал другое животное, программа признает поражение и просит ввести новое животное и вопрос, по которому его можно отличить от гончей или форели.
Таким образом, сначала программа совсем не блещет «сообразительностью». Но становится «умнее» в процессе игры, запоминая новых животных и новые вопросы. Она не всегда угадает ваше животное самым коротким способом, но через какое-то время она сможет изображать интеллект и «угадывать» ваше животное почти всегда. Это происходит потому, что с ростом ее базы данных она сможет «выслеживать ваше животное» все более и более уверенно.
Удовлетворяет ли игра в животных критериям?.
Вполне. Напомню их:.
• Вывод строк: программа должна давать пользователю указания и реагировать на ответы, которые он дает на ваши вопросы.
• Ввод: программа должна принимать от пользователя строки и чтото с ними делать. Диалоговый характер задачи требует некоторого (не слишком сложного) анализа введенных данных.
• Простой алгоритм: в зависимости от ответа программа выбирает разные вопросы. Таким образом, в зависимости от ответа «да» или «нет» выполняется обход структуры данных.
• Взаимодействие с постоянным хранилищем: программа должна где-то хранить текущую базу данных с животными и вопросами и считывать ее при запуске. Эти данные исчерпывают набор вопросов. Если программа не угадает животное, то должна будет обновить файл, записав в него новое животное и новый вопрос. Затем их надо сохранить для следующего сеанса работы.
• Прототип структуры данных: итак, чтобы получить результат, программе придется просмотреть связный список какого-то вида. Когда пользователь добавит новое животное и новый вопрос, программа должна будет добавить эти ссылки и обновить некоторые прежние, чтобы они указывали на новую информацию.
• Обработка ошибок: программа должна справиться с ситуацией, когда пользователь вводит пустые данные вместо требуемых реальных значений или файл данных оказывается поврежденным. Здесь большой простор для действий в зависимости от того, насколько дружелюбной пользователю вы хотите сделать программу. Например, как быть, если пользователь внезапно решит прекратить работу?.
• Абстракция и инкапсуляция: задачу можно легко обобщить, сделав ее игрой в угадывание овощей, минералов или известных личностей. Эти варианты должны зависеть только от загрузки разных файлов данных: язык должен позволять делать все с помощью одной и той же программы.
Заметьте, что я все очень упростил. Например, нельзя разрешать играть в эту игру одновременно нескольким людям, иначе задача сильно усложняется. Но даже для последовательной работы нескольких пользователей это неплохая задача программирования.
Языки, прошедшие тест.
Стечением времени я реализовал «игру в животных» на следующих языках:
FORTRAN.
BASIC.
APL.
Pascal.
FORTH
C.
Ada.
C++.
Следовало бы еще на Java, но.
Обычно мне требуется несколько часов, чтобы вспомнить, как организовать связный список, и выдать что-то готовое. Для того чтобы другие смогли играть в эту игру без моего надзора (это значит, что надо организовать какую-то обработку ошибок), мне обычно требуется несколько дней программирования. Возможно, это получалось бы у меня быстрее, если бы я мог найти код, оставшийся от предыдущих опытов, но это мне никогда не удается. Данное упражнение повторяется с периодичностью четырепять лет, которых достаточно, чтобы потерять предыдущий пример.
Вы не знаете, зачем нужен старый код? Разве нет поблизости проектной спецификации, документа на псевдокоде или диаграммы UML, с помощью которых можно написать реализацию, не заглядывая в прежний код?.
Ответ состоит из двух частей: да, я рисую диаграммы и пишу псевдокод, но мне приходится каждый раз делать это заново, потому что их я тоже не могу найти. Во-вторых, полезно посмотреть, как ты делал что-то на старом языке, когда нужно сделать то же самое на новом. Я бы предпочел иметь возможность найти старые диаграммы с псевдокодом и реализацию на старом языке. Все это вместе дало бы мне хороший начальный толчок. Если бы только я тщательнее вел учет. Ладно. Конечно, это вопиющее нарушение правил софтверной компании, но, может быть, не стоит преувеличивать размер этого греха, поскольку в сущности это лишь маленький личный программный проект?.
Самые необычайные из моих реализаций были сделаны на APL и FORTH. Те, кто работает с этими языками, могут догадаться о причинах, а объяснять остальным бесполезно. В списке подозреваемых отсутствуют LISP, Smalltalk, PL/I и COBOL. Ничто не мешает вам попробовать в них свои силы. Мне этого сделать не пришлось.
Кстати, выполняя это упражнение, вы знакомитесь также с особенностями среды разработки и доступными инструментами. Я, например, выяснил, что первые отладчики C++ были не блестящего качества; напротив, практичность Rational Environment была очевидна, и моя реализация на Ada была осуществлена в рекордно короткое время. Занимаясь этой задачей на FORTH, я научился перезагружать свою машину при каждой ошибке этапа исполнения.
Это ваша игра.
Лучший способ освоиться с новым языком программирования состоит в том, чтобы запрограммировать стандартную задачу, которая вам уже знакома. Это позволит вам изучить язык и узнать, «как это сделать» для известного набора практических вопросов. Моя стандартная задача служила мне долгие годы, и я считаю, что она заставляет решать минимальный, но полезный набор базовых проблем.
Резюме.
Ограниченность данного подхода в том, что он сосредоточен на проблемах низкого уровня программирования. Каждый новый язык обладает большей выразительной силой, и часто эта сила не проявляется в маленьких примерах. Например, не видно, каким образом данный пример способен помочь понять наследование в C++, хотя я уверен, что заинтересованный читатель найдет способ.
Мне неоднократно приходилось замечать, что большинство разработчиков работает с неким подмножеством новейшего языка, с помощью которого они и решают свои задачи. Лишь немногие эффективно применяют «мощные функции», превозносимые в книгах и звонкой рекламе, произрастающих на новом языке. Меня также беспокоит, что эти языки настолько усложнены, что когда искушенные пользователи применяют самые передовые возможности языка, то код становится непонятен программисту средней квалификации. Это особенно опасно, если учесть, что сопровождением кода в большинстве случаев занимается программист, менее компетентный, чем автор первоначального кода. Это не критика современных языков, а рекомендация менеджерам, которых интересует, чтобы выпускаемый ими код можно было сопровождать в течение длительного времени.
Далее мы переходим к главе 8, последней в этом разделе, посвященном отличиям, присущим производству ПО. Речь пойдет о малоизвестном искусстве, называемом «как выставить продукт за дверь». Это то, за что нам платят деньги, и мне горько признаться, что часто мы не слишком хорошо с этим справляемся.