Руководство по стилю программирования и конструированию по
Скачать 7.6 Mb.
|
ГЛАВА 34 Основы мастерства 821 Низкоуровневые процессы тоже важны. Если вы пишете псевдокод, после чего создаете соответствующий ему реаль- ный код, вы используете все преимущества нисходящего проектирования. Кроме того, так вы всегда будете иметь комментарии в коде, и потом вам не придется их писать. Чтобы проанализировать общие и частные процессы, нужно сделать паузу и об- ратить внимание на то, как вы создаете ПО. Это время тратится с пользой. Про- граммисты, руководствующиеся принципом «код — вот, что важно; сосредоточи- ваться надо на качестве кода, а не какого-то абстрактного процесса», поступают недальновидно, игнорируя горы экспериментальных и практических данных, свидетельствующих об обратном. Разработка ПО — процесс творческий. Если вы не понимаете сути этого процесса, ваш главный инструмент — мозг — часто ра- ботает вхолостую. Плохо организованный процесс заставляет тратить умствен- ные ресурсы впустую, хорошо — позволяет извлечь из них максимальную выгоду. 34.3. Пишите программы в первую очередь для людей и лишь во вторую — для компьютеров Ваша программа. Лабиринт нелогичных заключений, замусоренный замыс- ловатыми хитростями и нерелевантными комментариями. Сравните с МОЕЙ ПРОГРАММОЙ. Моя программа. Бриллиант детерминированной точности, воплотивший в себе совершенное равновесие между компактностью и эффективностью кода, с одной стороны, и превосходной удобочитаемостью, дополненной полным набором комментариев, — с другой. Сравните с ВАШЕЙ ПРОГРАММОЙ. Стэн Келли-Бутл (Stan Kelly-Bootle) Еще один лейтмотив данной книги — удобочитаемость кода. Общение с другими людьми — вот Святой Грааль самодокументирующегося кода. Компьютерам все равно, насколько удобочитаем ваш код. Они вообще лучше чи- тают двоичные команды, а не операторы высокоуровневых языков. Удобочитае- мый код нужно писать для того, чтобы он был понятен людям. Читабельность положительно влияет на такие аспекты программы, как: 쐽 понятность; 쐽 легкость выполнения обзоров; 쐽 уровень ошибок; 쐽 удобство отладки; 쐽 легкость изменения; 쐽 время разработки — следствие всего вышеперечисленного; 쐽 внешнее качество — следствие всего вышеперечисленного. Перекрестная ссылка Об итера- ции см. раздел 34.8. 822 ЧАСТЬ VII Мастерство программирования Удобочитаемый код писать ничуть не дольше, чем запутан- ный — по крайней мере в далекой перспективе. В работо- способности кода легче убедиться, если вы можете с легко- стью прочесть то, что написали, и уже одного этого доста- точно для работы над понятностью кода. Однако код чита- ют и во время обзоров. И при исправлении ошибок. И при изменениях программы. Наконец, его читают, когда кто-то другой пытается использовать фрагмент вашего кода в по- хожей программе. Работу над читабельностью кода не следует считать необя- зательной частью процесса разработки, и достижение удоб- ства во время написания за счет удобства во время чтения едва ли можно признать удачным решением. Лучше один раз написать хороший код, чем раз за разом читать плохой. «Что, если я просто пишу код для себя? Почему я должен делать его удобочитаемым?» Потому что через неделю-две вы будете работать над другой программой и подумаете: «Эй! Я уже написал этот класс на прошлой неделе. Я просто возьму мой старый протестированный отлаженный код и сэконом- лю время». Если код неудобочитаем, желаю удачи — она вам пригодится. Оправдывать написание нечитаемого кода тем, что над проектом работаете только вы, опасно. Помните, как ваша мать говорила: «Что, если твое лицо застынет в этом выражении?», а отец — «Ты играешь так, как тренируешься». Привычки влияют на всю вашу работу, и вы не можете изменить их, просто захотев этого, поэтому убе- дитесь, что используемые вами подходы, став привычками, вас устроят. Профес- сиональные программисты пишут удобочитаемый код, и точка. Поймите также, что утверждение, согласно которому код может принадлежать одному программисту, спорно. В связи с этим Дуглас Комер провел полезное раз- личие между личными и общими программами (Comer, 1981): «личные програм- мы» используются только программистом. Никто другой их не использует. Никто другой их не изменяет. Никто даже не знает об их существовании. Такие программы обычно тривиальны и крайне редки. А вот «общие программы» используются или изменяются не только автором, но и кем-то еще. Стандарты, которым должны соответствовать общие и личные программы, могут различаться. Личные программы могут быть плохо написаны и полны ограниче- ний, и это не затронет никого, кроме автора. Общие программы нужно писать более внимательно: их ограничения следует документировать, а сами программы сле- дует делать надежными и модифицируемыми. Опасайтесь превращения личных программ в общие, что происходит довольно часто. Преобразовать личную про- грамму в общую нужно до того, как она поступит в обращение, и один из аспек- тов этого преобразования — улучшение читабельности кода. Даже если вы думаете, что код будете читать только вы, в реальном мире высока вероятность того, что его придется изменять кому-то другому. Одно из исследований показало, что среднюю программу сопровождали 10 по- колений программистов, пока она не была переписана (Thomas, 1984). Програм- На заре программирования про- грамма считалась частной соб- ственностью программиста. Чте- ние чужой программы без спроса было не меньшей наглостью, чем чтение любовного письма. По сути этим и являлась програм- ма — любовным письмом про- граммиста компьютеру, полным интимных деталей, известных только партнерам. Программы заполнялись кличками домаш- них животных и сокращениями, столь популярными у влюблен- ных, живущих в благословенной абстракции и не замечающих больше никого во Вселенной. Всем остальным людям такие программы непонятны. Майкл Маркотти (Michael Marcotty) ГЛАВА 34 Основы мастерства 823 мисты, отвечающие за сопровождение, тратят от 50 до 60% времени, пытаясь по- нять код, и они по достоинству оценят ваши усилия, потраченные на его доку- ментирование (Parikh and Zvegintzov, 1983). В предыдущих главах были рассмотрены методики улучшения удобочитаемости кода: грамотный выбор имен классов, методов и переменных, тщательное фор- матирование, сокращение объема методов, сокрытие сложных логических тестов в булевых функциях, присваивание промежуточных результатов сложных вычис- лений переменным и т. д. Никакая одна методика не сделает запутанную программу читабельной, однако сумма многих небольших улучшений будет существенной. Если вы считаете, что какой-то код не нужно делать удобочитаемым, потому что никто другой никогда не будет иметь с ним дело, проверьте, не путаете ли вы причину и следствие. 34.4. Программируйте с использованием языка, а не на языке Не ограничивайте свое мышление только теми концепциями, которые непосред- ственно поддерживаются языком. Самые лучшие программисты думают о том, что они хотят сделать, после чего определяют, как достичь этих целей при помощи инструментов программирования, имеющихся в их распоряжении. Разумно ли создавать метод-член класса, не согласующийся с абстракцией клас- са, только потому, что он удобнее метода, обеспечивающего более высокую со- гласованность? Код должен как можно надежнее защищать абстракцию, форми- руемую интерфейсом класса. Не нужно использовать глобальные данные или операторы goto только потому, что их поддерживает язык. Вы можете отказаться от опасных возможностей и применять вместо них соглашения программирова- ния, компенсирующие слабости языка. Выбирать самые очевидные пути — зна- чит программировать на языке, а не с использованием языка; в программирова- нии этот выбор эквивалентен вопросу: «Если Фредди спрыгнет с моста, прыгнете ли вы за ним?» Подумайте о своих целях и решите, как лучше всего достичь их, программируя с использованием языка. Ваш язык не поддерживает утверждения? Напишите собственный метод assert(). Пусть он работает не совсем так, как встроенный assert(), но вы все же сможете задей- ствовать большинство преимуществ этого подхода. Ваш язык не поддерживает пе- речисления или именованные константы? Прекрасно: определите собственные перечисления и именованные константы путем дисциплинированного использо- вания глобальных переменных, дополненного ясными конвенциями именования. В крайних случаях — особенно в новых технологических средах — инструменты бывают такими примитивными, что разработчикам приходится значительно из- менять свой желательный подход к программированию. Иногда это заставляет уравновешивать желание программировать с использованием языка и несуществен- ные сложности, возникающие из-за того, что особенности языка делают ваш подход слишком неуклюжим. Однако, попав в такие условия, вы сможете извлечь даже большую выгоду из соглашений программирования, помогающих избавиться от наиболее опасных возможностей среды. Как бы то ни было, обычно несоответ- 824 ЧАСТЬ VII Мастерство программирования ствие между тем, что вы хотите сделать, и тем, что уже поддерживают инструмен- ты, невелико и заставляет идти лишь на небольшие уступки. 34.5. Концентрируйте внимание с помощью соглашений Набор соглашений — один из интеллектуальных инструмен- тов управления сложностью. В предыдущих главах мы го- ворили о специфических конвенциях. В этом разделе я по- ясню на примерах общие преимущества соглашений. Многие аспекты программирования в чем-то произвольны. Какой длины отступ делать перед циклом? Как форматиро- вать комментарии? Как упорядочивать методы класса? Боль- шинство подобных вопросов не имеет одного правильного ответа. Конкретный ответ на такой вопрос менее важен, чем его согласованность. Соглашения избав- ляют программистов от необходимости снова и снова отвечать на те же вопросы и принимать все те же произвольные решения. В проектах, реализуемых многи- ми программистами, соглашения предотвращают замешательство, возникающее, когда разные программисты принимают разные решения. Конвенция лаконично сообщает важную информацию. В случае соглашений име- нования один символ может обеспечить различие между локальными перемен- ными, глобальными и переменными класса, регистр букв может указать на типы, именованные константы и переменные. Соглашения использования отступов могут охарактеризовать логическую структуру программы. Соглашения выравнивания указывают на связь операторов. Соглашения защищают от известных опасностей. Вы можете задать соглашения для исключения применения опасных методик, для ограничения их использова- ния в ситуациях, когда эти методики действительно нужны, или для компенсации их известных недостатков. Так, вы можете исключить опасность, запретив при- менение глобальных переменных или объединение нескольких команд в одной строке. Вы можете компенсировать слабости опасных методик, потребовав за- ключать сложные выражения в скобки или устанавливать указатели в NULL сразу после их освобождения для предотвращения «зависания» указателей. Соглашения делают более предсказуемыми низкоуровневые задачи. Наличие со- глашений обработки запросов памяти и обработки ошибок или соглашений вво- да/вывода и создания интерфейсов классов добавляет в код выразительную струк- туру и делает его понятнее программистам, знающим об этих соглашениях. Как я уже говорил, одно из главных преимуществ устранения глобальных данных со- стоит в исключении потенциальных взаимодействий между разными классами и подсистемами. Программист, читающий код, примерно представляет, чего мож- но ожидать от локальных данных и данных класса, но едва ли он может опреде- лить, что изменение глобальных данных портит какой-то бит в коде подсистемы, находящейся на обратной стороне программы. Глобальные данные вносят в код неопределенность. Хорошие соглашения позволяют вам и людям, читающим ваш код, больше принимать как данное. Число деталей, которые нужно охватить, умень- шается, а это в свою очередь облегчает понимание программы. Перекрестная ссылка О полез- ности соглашений в контексте форматирования кода см. под- разделы «Насколько важно хо- рошее форматирование?» и «Цели хорошего форматирова- ния» раздела 31.1. ГЛАВА 34 Основы мастерства 825 Соглашения могут компенсировать недостатки языков. Если язык не поддержи- вает именованные константы (к таким языкам относятся Python, Perl, языки обо- лочек UNIX и т. д.), конвенция позволяет провести различие между переменны- ми, допускающими и чтение, и запись, и переменными, служащими для эмуляции констант, предназначенных только для чтения. В качестве других примеров ком- пенсирования недостатков языка при помощи соглашений можно назвать согла- шения дисциплинированного использования глобальных данных и указателей. В крупных проектах программисты иногда злоупотребляют конвенциями. Они создают так много стандартов и правил, что их запоминание само по себе стано- вится полноценной работой. Но в небольших проектах программисты из-за пло- хого понимания достоинств разумных соглашений обычно впадают в другую крайность. Поймите подлинную ценность соглашений и извлекайте из них выго- ду; используйте их для структурирования тех областей, которые страдают от не- достатка структуры. 34.6. Программируйте в терминах проблемной области Другим специфическим методом борьбы со сложностью является работа на мак- симально высоком уровне абстракции. Один способ достижения этой цели за- ключается в работе в терминах проблемы программирования, а не ее компьютер- ного решения. Высокоуровневый код не должен включать подробных сведений о файлах, сте- ках, очередях, массивах, символах и подобных объектах, имеющих имена вроде i, j и k. Высокоуровневый код должен описывать решаемую проблему. Он должен быть наполнен описательными именами классов и вызовами методов, ясно харак- теризующими выполняемые действия, а не подробными сведениями о том, что файл открывается в режиме «только для чтения». Высокоуровневый код не должен быть загроможден комментариями, гласящими, что «здесь переменная i представляет индекс записи из файла о сотрудниках, а чуть позже она используется для индек- сации файла счетов клиентов». Это неуклюжая методика программирования. На самом высоком уровне программы не нужно знать, что данные о сотрудниках представлены в виде записей или хра- нятся в файле. Информацию, относящуюся к этому уровню детальности, надо скрыть. На самом высоком уровне вы не должны иметь понятия о том, как хра- нятся данные. Вы не должны читать комментарии, объясняющие роль перемен- ной i и то, что она используется с двойной целью. Вместо этого вы должны ви- деть две переменные с выразительными именами, такими как employeeIndex и clientIndex. Разделение программы на уровни абстракции Очевидно, что на некотором уровне надо работать и в терминах реализации, но вы можете изолировать эти части программы от частей, разработанных в терми- нах проблемной области. Проектируя программу, обдумайте уровни абстракции (рис. 34-1). 826 ЧАСТЬ VII Мастерство программирования Рис. 34-1. Программа может быть разделена на несколько уровней абстракции. Удачное проектирование позволяет программистам проводить значительную часть времени, сосредоточившись только на верхних уровнях, игнорируя более низкие уровни Уровень 0: возможности операционной системы и машинные команды Если вы программируете на высокоуровневом языке, можете не беспокоиться о самом низком уровне: язык позаботится об этом автоматически. Если же вы ис- пользуете низкоуровневый язык, попробуйте создать ради своего удобства более высокие уровни, хотя многие программисты этого не делают. Уровень 1: структуры и средства языка программирования Структуры языка программирования — это элементарные типы данных, управля- ющие структуры и т. д. Кроме того, большинство популярных языков снабжено дополнительными библиотеками, предоставляют доступ к вызовам ОС и т. д. Вы используете эти структуры и средства естественным образом, так как программи- ровать без них невозможно. Многие программисты никогда не поднимаются выше этого уровня абстракции, чем значительно осложняют себе жизнь. Уровень 2: низкоуровневые структуры реализации Низкоуровневые структуры реализации относятся к чуть более высокому уровню, чем структуры, предоставляемые самим языком. В большинстве своем это опера- ции и типы данных, которые вы изучали в вузе: стеки, очереди, связные списки, деревья, индексированные файлы, последовательные файлы, алгоритмы сортиров- ки, поиска и т. д. Если вы будете писать программу полностью на этом уровне, вам придется работать со слишком большим числом деталей, чтобы победить в битве со сложностью. Уровень 3: низкоуровневые элементы проблемной области На этом уровне вы имеете дело с примитивами, нужными для работы в терминах проблемной области. Это клей, скрепляющий нижележащие структуры компью- терных наук и высокоуровневый код проблемной области. Чтобы писать код на этом уровне, вы должны определить словарь проблемной области и создать стро- ительные блоки, годные для решения поставленной задачи. Во многих приложе- ниях этим уровнем является уровень бизнес-объектов или уровень сервисов. ГЛАВА 34 Основы мастерства 827 В качестве элементов словаря и строительных блоков данного уровня выступают классы. Возможно, эти классы слишком примитивны, чтобы их можно было за- действовать для решения проблемы непосредственно на этом уровне, однако они формируют каркас, на основе которого можно решить проблему, используя классы более высокого уровня. Уровень 4: высокоуровневые элементы проблемной области Этот уровень формирует абстракцию, позволяющую работать с проблемой в ее собственных терминах. Код, написанный на этом уровне, должен быть частично понятен даже людям, далеким от программирования — возможно, и вашим заказ- чикам. Он будет слабо зависеть от специфических аспектов языка программиро- вания, потому что вы будете использовать для работы над проблемой собствен- ный набор средств. Так что на этом уровне ваш код больше зависит от средств, созданных вами на уровне 3, чем от возможностей языка. Детали реализации уже должны быть скрыты на два уровня ниже — на уровне структур компьютерных наук, чтобы изменения оборудования или ОС совсем не влияли на этот уровень. Выразите в программе на этом уровне пользовательское представление о мире, потому что когда программа изменяется, она изменяется в терминах пользователя. Изменения проблемной области будут сильно влиять на этот уровень, но вы сможете легко адаптировать к ним программу, создавая но- вую версию на основе строительных блоков предыдущего уровня. Многие программисты находят полезным дополнение этих концептуальных уров- ней другими, перпендикулярными «уровнями». Например, типичная трехуровневая архитектура пересекает описанные выше уровни, предоставляя дополнительные средства интеллектуального управления аспектами проектирования и кодом. Низкоуровневые методики работы в проблемной области Даже не выработав полного архитектурного подхода к словарю проблемной об- ласти, вы можете использовать многие методики этой книги для работы в тер- минах проблемы реального мира, а не ее компьютерного решения. 쐽 Используйте классы для реализации структур, значимых в проблемной области. 쐽 Скрывайте информацию о низкоуровневых типах данных и деталях их реа- лизации. 쐽 Используйте именованные константы для документирования смысла строк и численных литералов. 쐽 Присваивайте промежуточным переменным промежуточные результаты вычис- лений с целью документирования этих результатов. 쐽 Используйте булевы функции для пояснения сложных булевых тестов. 34.7. Опасайтесь падающих камней Программирование не является ни полностью искусством, ни полностью наукой. В своей обычной форме оно представляет собой «мастерство», занимающее про- межуточное место между искусством и наукой. В лучшем случае это инженерная дисциплина, основанная на синергической интеграции науки и искусства 828 ЧАСТЬ VII Мастерство программирования (McConnell, 2004). Чем бы программирование ни было — искусством, ремеслом или инженерной дисциплиной, создание работающей программы требует изрядной доли рассудительности. А для этого нужно обращать внимание на широкий диа- пазон предупреждающих знаков — тонких намеков на проблемы в вашей програм- ме. Предупреждающие знаки в программировании указывают на возможные про- блемы, но обычно они не настолько очевидны, как дорожный знак, предупреж- дающий о камнепадах. Слова «Это по-настоящему хитрый код» обычно предупреждают о том, что код плох. «Хитрый» код — это другое название «плохого» кода. Если код кажется вам хит- роумным, подумайте, не переписать ли его, чтобы он таким не был. Класс, число ошибок в котором превышает средний уровень, — тоже предупреж- дающий знак. Несколько классов, подверженных ошибкам, обычно оказываются самой дорогой частью программы. Если число ошибок в каком-то классе превы- шает средний уровень, такая ситуация, вероятно, сохранится и в будущем. Поду- майте о том, чтобы переписать его. Если бы программирование было наукой, с каждым предупреждающим знаком был бы связан конкретный, хорошо определенный способ исправления проблемы. Но так как программирование еще и мастерство, предупреждающие знаки просто указывают на проблемы, которые вы должны рассмотреть. Переписывать хитро- умный код или улучшать класс, подверженный ошибкам, нужно не всегда. Как аномальное число дефектов в классе предупреждает о низком качестве клас- са, так и аномальное число дефектов в программе свидетельствует о неэффектив- ности процесса разработки. Хороший процесс не привел бы к получению дефек- тного кода. Он включил бы проверку архитектуры, за которой последовали бы обзоры архитектуры, проектирование с обзорами проекта и кодирование с об- зорами кода. Ко времени тестирования кода большинство ошибок было бы устранено. Для достижения высочайшей производительности труда нужно рабо- тать не просто усердно, но и разумно. Большой объем отладки предупреждает о том, что программисты не работают разумно. Написать большой фрагмент кода за день и потратить две недели на его отладку — это и есть неразумная работа. Метрики проектирования также могут быть предупреждающими знаками. Боль- шинство таких метрик — это эвристические правила, характеризующие качество проектирования. Если класс содержит более семи членов, это не всегда означает, что он плохо спроектирован, но предупреждает о том, что класс сложен. Более 10 точек принятия решения в методе, более трех уровней логической вложенно- сти, необычно большое число переменных, высокая степень сопряжения одного класса с другими или низкий уровень внутреннего сопряжения класса или мето- да — все это предупреждающие знаки. Они не всегда означают, что класс спроек- тирован плохо, но наличие любого из них должно заставлять вас взглянуть на класс скептически. Любой предупреждающий знак должен заставить вас сомневаться в качестве програм- мы. Как говорит Чарльз Саундерс Пирс (Charles Saunders Peirce), «сомнение — это неловкое и неприятное состояние, от которого мы стараемся освободиться, перей- дя в состояние убежденности». Рассматривайте предупреждающие знаки как «при- чины сомнения», побуждающие искать более приятное состояние убежденности. |