Текст книги "The Programmers' Stone (Программистский камень)"
Автор книги: Alan Carter
Соавторы: Colston Sanger
сообщить о нарушении
Текущая страница: 8 (всего у книги 13 страниц)
Уменьшение сложности и последовательное ужимание
Все интересные системы, от игровых до расчетных, содержат операции, делающие состояние более сложным, а другие его упрощают. Большинство формальных процессов и обучение программированию фокусируются на накоплении массы. Для сбрасывания лишнего веса и получения преимуществ, которое оно приносит, мы должны проявить инициативу. Нам может быть поставлена задача написать функцию, но никто не поставит задачу обобщить ее, чтобы она решала три других.
Огромные глыбы затмения всегда ходят парой, одна для сгибания вещей, другая для их разгибания. Это распространяется на выявление требований, когда у пользователя обычно появляется желание воспроизвести функциональность существующей системы, включая процедурные следствия ограничений существующей системы и ее решения! Иногда это называют "деградирующей практикой".
Проблема, возникающая вместе с объектными библиотеками, состоит в том, что объекты очень хорошо инкапсулируют (скрывают) свою реализацию. Это позволяет нам использовать иерархии классов на самом деле ничего не зная о их содержимом. Поэтому приходится продираться через несколько слоев классов, каждый из которых (возьмем пример из Географических Информационных Систем – Geographical Information Systems) имеет дело с координатной системой, просто для того, чтобы поставить значок на карте. Никаких проблем не возникает до тех пор, пока мы впервые не попытаемся поставить несколько сот значков, и обнаружим, что "простое присваивание", которым мы так восхищались, требует столько вычислительной мощности, что для перерисовки экрана нужно полчаса.
Проект, который регулярно не проверяет свою иерархию классов, чтобы гарантировать, что внутренние представления естественны и стандартны, и что дизайн соответствует встречающимся на практике ситуациям, может неожиданно оказаться по горло в ... воде из-за скрытой цены повторного использования.
Как всегда, самое лучшее оружие от распухания – это концептуальная целостность, достигаемая сравнением мысленных моделей проекта.
Бесконечная деградация «программных архитектур»
Была команда, которую заказчик попросил создать среду реального времени, которая могла бы поддерживать небольшие процессы, стыкуемые через средства ввода/вывода, что-то типа графической оболочки для управления сложной системой трубопроводов. Если классифицировать эту задачу, это работа по системному программированию. Эта команда решила быть Профессиональной, и стала использовать Надлежащие Методы. Поэтому они пошли и сделали объектно-ориентированную модель этих вещей из Требований Пользователя используя инструменты, автоматизирующие подход Шлера-Меллора (Shlear-Mellor). Но была небольшая трудность, поскольку проблема была из другой области, зато они подогнали ее под Надлежащую Методологию. Они ведь могли использовать генератор кода! Они нажали кнопку и получили целую кучу склеенных классов. Каждый из них просто вызывал процедуру из API из «Программной Архитектуры», слоя, который требовался в подходе, что позволяло приложению работать на определенном типе компьютерной системы. В этом случае Программная Архитектура стала бы системным средством предоставления графического интерфейса для оболочки, работающей со сложной системой трубопроводов!
Как картостроители, мы можем видеть, что произошло с этим образчиком тупого Профессионализма и Следования Процедуре паковщиков. Команда извлекла не тот пакет знаний и использовала хорошо продуманный и замечательно автоматизированный подход и язык в совершенно неправильном контексте. Подход Шлера-Меллора предназначен для задания функционирования прикладных программ, а не для системного программирования. Говоря простым языком, они не смогли увидеть, что классификация проблемы была проведена неправильно, поскольку у них не было здравого смысла, что есть результат недостатка морали или врожденного идиотизма. Мы предпочли бы сказать, что они были принуждены думать, что им нельзя думать непосредственно о работе, но сначала все смести и найти костыль. Потом они могли видеть проблему только через подход, даже если он плохо соответствовал. Затем подход уровня приложений представил их проблему на прикладном языке. Поэтому им в голову не приходило подумать, какого сорта программу они пишут, и заметить, что это системная оболочка, а не прикладная программа. Для паковщиков Профессионализм всегда означает взять зубную щетку и палочки для еды в руку и не замечать, что зубная щетка попала в нос, хотя все в полном соответствии с церемонией!
По мере того, как все становилось все более абсурдным, росло напряжение и команда все настойчивее обороняла свой Профессионализм. Один раз на встрече некто, ожидая хотя бы немного прагматизма, представился как "программист компьютеров" (computer programmer). Результат был замечательный. Вся команда глядя в пол бубнила под нос "Программный Инженер... Компьютерный Ученый..." (Software Engineer... Computer Scientist...), как будто тот совершил значительную социальную ошибку. Два члена команды позднее объясняли, что они не нашли создание "просто кода" интересным, и их Профессиональный Интерес заключался в Применении Процесса.
Очень тяжело заниматься системным программированием применяя некие лежащие перед тобой инструкции к компьютеру. Это ведет к стрессу, поскольку невозможно. Поэтому очень важно многословно демонстрировать свой Профессионализм руководству, чтобы никто не смог отделить зерна от плевел в этом вечном хаосе.
Вы не сможете писать компьютерные программы, если ваша стратегия состоит в замысловатой игре по передаче пакетов. Даже если придерживаться характерной стратегии паковщиков передавать документ соседу слева "для рецензии". Менеджер проекта, который пишет описание задачи, показывая входы и выходы задачи, часто может держать такие вещи под контролем, но только в том случае, если действия соответствуют атомам познания проекта и хорошо разбиты на части и при условии, что выходы на самом деле ближе к обработчику, либо непосредственно, либо путем информирования другой стороны, чем входы.
Картостроители тоже могут извлечь интересный урок из подобных историй. Нужно быть предельно осторожным, включаясь в игры по функциональной передаче пакетов. Сложность никуда не исчезает, и функциональность не появляется из ничего. Сложность может быть выражена более просто, если копнуть глубже. Ты узнаешь, когда ты этого добился. Ее можно также избежать, удалив часть сложности. Ты также узнаешь, когда ты этого добился. Когда показалось, что огромная глыба неуклюжести куда-то пропала, вероятно ты просто переместил ее куда-то в другое место своей структуры. И это хорошо, поскольку когда ты обнаружишь ее вновь, то ты получишь две точки зрения на ту же проблему, а это может привести к большим открытиям. Но не питайте привязанность к какому-то решению, которое создает видимость, что вся сложность магически исчезает – она просто неожиданно проявится в другом месте и испортит весь день. Это проявляется на любом уровне, начиная с выявления требований и заканчивая отладкой. Ошибки никуда не деваются. Те ошибки, которые появились, а потом неожиданно исчезли сами собой – самые коварные ошибки. Возьми старую реализацию, найди их и убедись, что таких же ошибок нет в последней реализации.
В качестве практического примера того, что функциональность не появляется по мановению волшебной палочки, рассмотрим проблему атомарности (atomicity). В многозадачных системах многие процессы исполняются без возможности управления временем приостановки, чтобы дать шанс выполниться другим процессам. Иногда двум процессам требуется координация, например для управления периферийными устройствами. Проблема в том, что один из процессов может обнаружить, что периферийное устройство свободно и сделать пометку о его захвате. Но в промежутке между этими двумя событиями второй процесс тоже может сделать проверку и обнаружить, что устройство свободно и тоже начнет делать пометку о захвате. Один процесс просто перепишет пометку другого и оба будут пытаться получить доступ к периферийному устройству одновременно. Не имеет значения, как это организуется в пользовательских процессах, невозможно совместить проверку и установку как единую, "атомарную" операцию, независимо от заумности пользовательского процесса. Ее можно инкапсулировать в функцию GetLock(), но она все равно должна добыть атомарность из операционной системы. Если процессор поддерживает многозадачность на уровне железа, как большинство современных процессоров, нам понадобится атомарная операция, содержащаяся в наборе инструкций, такая как инструкция TAS в процессорах серии 68000 фирмы Motorola.
Ничего этого нельзя увидеть предполагая, что нельзя переходить на использование расслоения или применять специализированные языки. Действительно, если некто полагается на специфический код операции просто для захвата принтера, то, по-большому счету, необходимо разбиение на слои! Это просто означает, что для достижения максимального упрощения мы используем оптимальный уровень специализации – мы не делегируем невозможное призрачным ящикам и не основываем дизайн на ложных предпосылках.
Аудит качества
Для картостроителя процесс – это протокол взаимодействия со своими коллегами во времени и пространстве, а не предварительно написанный свод правил, исполнение которых контролируется. Он предоставляет средства, а не требует тупого подчинения. Чтобы отчетливо подчеркнуть различие, нам следует в правильном духе рассмотреть аудит качества.
В обычной модели паковщика аудит – это суровое испытание. По мере приближения аудита менеджеры работают с мрачным предчувствием, работники стремятся сбежать в отпуск или в командировку, либо прикидываются больными, лишь бы избежать аудита. Когда аудиторы набрасываются как коршуны, они смотрят на членов команды как на врагов, и принуждают их к подразумеваемому подтверждению совершенства процесса и того, что все недостатки, которые они обнаружат, – нарушения, совершенные людьми. Отдельные работники становятся мальчиками для битья, несущими наказание за ошибки организации, которые они не контролируют. Это классическая ситуация, заставляющая людей глотать успокоительное перед приходом на работу. Нет сомнения, что это модель, не смотря на то, что ее редко так характеризуют, поскольку стандартное для менеджеров "Краткое руководство для погонщика" (Bogeyman Briefing) включает советы типа: "Не выдавай информацию добровольно. Формулируй кратко. Если тебя спрашивают, где план управления проектом, скажи, что в Хранилище (Registry)". Адвокат должен давать похожие советы клиенту, подвергаемому перекрестному допросу!
В ISO 9001 нет абсолютно ничего , что требовало бы оправдать ритуал такого вот пути совершенствования. У нас вообще нет проблем с ISO 9001. В действительности мы говорим, что разговор о "следовании ISO 9001" в то время, когда мы даже не способны применять с положительной мотивацией сам ISO 9001, превращает ту же самую старую глупость в еще один "радикально новый прорыв в науке менеджмента". Ну, а что думает об аудите качества команда счастливых картостроителей?
Во-первых, объектом пристального внимания однозначно должен стать процесс сам по себе. Мы должны оказать любезность работникам, предполагая, что они делают свое дело добросовестно, поскольку это всегда так, даже когда их держат за полных идиотов. Мы, таким образом, предполагаем, что любая проблема, по умолчанию, – это системный просчет процесса. Нехорошо сваливать повторяющиеся крушения самолетов на "ошибки пилота" – очевидно, что нуждается в перепроектировании эргономика кабины.
Во-вторых, выполняемые аудиторами сравнения, должны проводится между возможностями процесса и тем, что требует от пользователей бизнес. Одна из худших вещей, возникающая от враждебного аудита, – это то, что разногласия при определенных обстоятельствах могут легко вылиться в оргвыводы. Например, большинство процессов содержат пункт, говорящий, что в записях у кадровиков должны делаться отметки о прохождении обучения. Это вполне соответствует полуквалифицированной работе, когда рабочие могут получать "билетики", позволяющие им выполнять специфические задачи. В транспортном отделе часто требуется получать сертификаты из-за законов, и там проблема немного другая. В программирующей команде формальные курсы обучения дают очень мало по сравнению с навыком, получаемым в команде, поскольку почти никогда не имеют отношения к делу. Повышение квалификации происходит либо на работе, либо за счет свободного времени работника. Многие программисты проводят по нескольку сотен часов в год за самообучением дома. Авторы основанных на "или иначе" процессах должны помнить, что наниматель даже не смеет потребовать от работника разглашения, что же он изучает дома, поэтому не стоит грубить ему, уходя от вопроса, поскольку менеджерам проектов действительно нужна эта информация и им приходится спрашивать вежливо. Так что проверка бессловесной покорности глобальному механизму бесполезна по сути и приведет только к спорам об интерперетации. Вместо этого аудитор должен оценить локальные потребности и удостовериться в применимости процесса в свете этих потребностей.
В-третьих, аудитора нужно рассматривать как коллегу по бизнесу со своим положительным вкладом в работу. Эти люди видят сотни потребностей бизнеса и картотек, автоматизированных и ручных. Если бы глупейшая война между проверяющим и проверяемыми смогла превратиться в совместную критику процесса, проверяемые получили бы возможность открыто говорить о своих проблемах, вместо игры в молчанку, как это советует большинство менеджеров. Только тогда, когда они знают, в чем заключаются настоящие проблемы, аудиторы смогут покопаться в обширном опыте и предложить решения, которые, как они видели, срабатывали у других.
Аудиторам качества не следует быть счетчиками зерна, единственный положительный вклад которых заключается во внедрении изысканных ритуалов для компенсации неестественной сложности процесса. Их роль должна идти гораздо дальше. Роберт Хайнлайн (Robert Heinlein) сказал, что цивилизация строится на библиотечной науке, а аудиторы качества – это современные библиотечные ученые инженерной индустрии. Они могут сказать нам, как дешевле всего сохранить данные так, чтобы мы смогли найти их позднее, при условии, что мы знаем что ищем.
Глава 5. Принципы разработки
Простая и надежная среда
Средства разработки состоят из всех тех инструментов (включая текстовые процессоры), которые используют программисты, а также машин и сетевой инфраструктуры, на которой они работают. Хорошие средства разработки должны быть как можно проще. Как и в программах, которые вы разрабатываете: чем больше сложности, тем больше места для проблем, тем выше стоимость эксплуатации.
Хорошее общее правило – держать всю свою работу, включая средства конфигурации (в файлах скриптов, если нужно) в виде простого текста. Имейте возможность при необходимости стереть все, кроме исходного текста, и автоматически перекомпилировать.
Самая главная задача репозитария и системы управления конфигурацией – обеспечить вашу безопасность. Каждый добавляемый элемент сложности увеличивает опасность падения системы. Команда, перекладывающая контроль на систему управления конфигурацией с клиент-серверной архитектурой, рискует потерять проект из-за потери ссылочной целостности в системе. Получающийся хаос, когда вся работа останавливается, понуждает найти способ обеспечения безопасности в создании резервной копии, люди начинают выяснять, что необходимо переделать заново, и дух падает. Не так сложно построить хорошую систему управления конфигурацией на основе скриптов с использованием списков рассылки, на основе чего-то простого, типа SCCS или RCS, для обеспечения контроля версий.
Та же самая логика, что целью является безопасность, и поэтому простота увеличивает надежность и безопасность, когда система и пользователи в стрессе, применима и к созданию резервных копий. Когда что-то не так, главное – это знать, как откатить систему в нормальное состояние, в котором она была некоторое время назад. Инкрементное резервирование с гибкой стратегией отслеживания изменений дерева каталогов может уменьшить доверие к тому, что все работает нормально, даже когда это так. Или, скорее, уменьшит. Команда, перешедшая к простому тексту, записывающая все на ленту и меняющая ленту каждую ночь, знает, где все это находится, и на этом надежном фундаменте способная достигать прогресса в выполнении настоящей работы, действительно умнее, чем любящая сложности команда, проводящая месяц только в попытках организовать работу.
Делай проще. Никогда не латай ничего – ты никогда не знаешь, нашел ли ты все проблемы. Сотри и загрузи заново. Всегда будь способен переформатировать свой диск, переинсталлировать свои инструменты, восстановить тексты из репозитария, переконфигурировать и перекомпилировать. Это дает полную безопасность и сохраняет все то время, которое ушло бы на суету по поводу вирусов. Кого это волнует?
Типы систем
Один из самых важных вопросов, который нужно задать о новой системе, или даже о старой системе, с которой пришлось столкнуться, – какого вида эта система? Никто не станет даже пытаться расположить колонию хиппи вокруг плаца или военный лагерь в виде хаотически расположенных вигвамов, соединенных тропинками! Любая система может обладать более чем одним признаком из перечисленных ниже, хотя некоторые взаимно исключают друг друга. Эти, вероятно полезные, признаки – грубые категории, встречающиеся на практике. Они не выведены из какой-то лежащей в основе теории. Примеры типов систем такие:
Монолитная. Централизованная обработка, а также оффлайновые соединения с пользователями либо примитивные терминалы – вот что такое монолит. Пользователи находятся либо в том же здании либо подключены по постоянной выделенной линии. Это прекрасный способ проделать обработку громадного количества коммерческих данных, когда к системе подключены промышленные средства ввода документов, огромные хранилища данных на дисках и лазерные принтеры, выплевывающие страницы на несколько футов в воздух. У монолитов свои проблемы – пользователи, например, часто лишены возможности доступа к резервной копии! Но это сглаживается высокой степенью контроля над системой. Устойчивое к сбоям оборудование (включающее RAID и резервирование питания) и сложные технологии серверов баз данных – вот где имеют преимущество такие системы.
Клиент-Сервер. Распределяет обработку среди пользователей для работы, которая должна быть сосредоточена в одном месте. Предоставляет хорошее разбиение на уровни, разделяя хранение, обработку и взаимодействие с пользователем (HCI – Human-Computer Interface, человеко-машинный интерфейс). Допускает локальную специализацию по функциям и нескольких поставщиков, и, следовательно, сохраняет инвестиции при последующем расширении. Требует (и, следовательно, использует) более сложную сеть, чем монолит, но лучше приспособлена к интерактивной работе, давая максимально возможному числу пользователей выполнять обработку локально, используя средства, наиболее соответствующие потребностям пользователей. Все клиент-серверные архитектуры внутри себя прячут монолит.
Интерактивная. Позволяет пользователю вступать в диалог с системой. Незаменима, когда требуется быстрый отклик, либо при работе с широкой публикой. В наше время обычно построены на графическом интерфейсе. Состояние системы постоянно изменяется, поэтому требуется журналирование, либо периодически будет возникать риск потери данных. Проблема распределения нагрузки – важный момент, поскольку число пользователей меняется, и нагрузка регулярно скачет от нормальной до пиковой.
Пакетная. Хотя их часто считают устаревшей стратегией с использованием перфокарт, пакетные системы просты, надежны, великолепно себя ведут при неустойчивой связи и масштабируются лучше, чем интерактивные системы.
Управляемая событиями. Реагирует на события из внешнего мира. Приложения с графическим интерфейсом большую часть времени находятся в ожидании, пока пользователь что-нибудь нажмет, чтобы отреагировать на событие. «Однорукие бандиты» (игровые автоматы) – тоже управляемые событиями системы, как и сигнализация. У управляемых событиями систем сложное пространство состояний и большая чувствительность к вводимым данным. Часто для них ограничивается время ответа на событие. Если проблему можно представить как не управляемую событиями систему, то лучше так и сделать.
Управляемая данными. Аналогична управляемой событиями и пакетной, но с ясными потоками данных через каждую подсистему, где наличие входных данных – это событие-триггер, заставляющее каждую подсистему выполнять свой цикл обработки. Управляемые данными системы гибче пакетных, поскольку размер пакета может изменяться динамически, но они обладают надежностью пакетных систем, поскольку мы всегда можем узнать, как далеко мы зашли в обработке каждого пакета. Каждая подсистема может организовывать элементарную операцию, выдающую информацию и удаляющую входные данные, так что даже при восстановлении после сбоя питания система сразу готова к работе, поскольку состояние системы всегда устойчиво. Системы электронной почты – пример систем, управляемых данными.
Оппортунистическая. (рассчитывающая на благоприятную возможность)Этот тип систем никогда не пострадает от ошибок при передаче, поскольку они используют каналы только тогда, когда это возможно. В действительности, большинство офисов оппортунистические, поскольку они построены на основе Ethernet. Данные хранятся в буфере до тех пор, пока локальный передатчик не передаст их без конфликтов (collision).
Штурманская (Dead reckoning). Пытается проследить каждый шаг пользователя. Часто представляется желательной, поскольку позволяет обеспечить сильную проверку данных пользователя, но может оказаться очень хрупкой, что приводит к шуткам типа знаменитой: «Бесполезно нажимать на это, компьютер говорит, что этого тут нет!»
Сходящаяся в одну точку (Convergent). Тут не интересно отслеживать каждый шаг происходящего в реальном мире процесса, здесь внимание направлено на регистрацию изменений состояний в ключевых точках и интегрировании данных в виде аккуратной картины реального мира в некоторый момент в прошлом, и прогрессивно ухудшающейся аппроксимацией по мере приближения к настоящему. Мобильные пользователи, которые закачивают данные со своих ноутбуков в корпоративную сеть – хороший пример. Мы очень точно знаем, как много мы продали на прошлой неделе, очень приблизительно знаем о вчерашнем дне, но мы не получили еще данные от Джона и Джилла, поэтому не знаем почти ничего о сегодняшнем дне.
На гребне волны (Wavefront). Системы, работающие по мере появления событий. Управление освещением или телефонная коммутация – вот примеры. При сбоях мы больше заинтересованы в быстром восстановлении работы, чем обеспокоены потерей данных.
Ретроспективная (Retrospective). Связанная с поддержанием аккуратной записи прошлого. Избежание потерь данных обычно очень важно. Пример – бухгалтерские системы.
Обработка ошибок – лимфатическая система программы
Часто говорят, что следует перехватывать сообщения об ошибках, но в этом мало пользы, если неизвестно, что же с ними собираются делать. Обработка ошибок составляет такую же часть структуры программы, как и логика обработки нормальных ситуаций, но не настолько же почитаема. Эта связь скорее похожа на взаимодействие кровеносной и лимфатической систем в теле. Вам необходимо предусматривать обработку ошибок на каждой стадии проектирования. Например, нет никакого смысла регистрировать сообщения об ошибках записи в журнал ошибок в процедуре обработки ошибок!
Концептуальная целостность требует, чтобы вы определили общий подход к обработке ошибок, используемый по всему проекту. Как вы будете сообщать об ошибках? Какие идиомы будут применять программисты для элегантной проверки ошибок без нарушения главного потока управления? Совершенно необязательно делать второй вызов, чтобы проверить, возникла ли ошибка или что там было – иначе код будет распухать. В идеале ошибки должны проверяться при выходе из функции, чтобы использовать краткие идиомы, что приводит к получению понятного кода и, как следствие, плато качества:
if((fp = fopen(...)) == NULL) { // Error }
или
if(!DoTheBusiness()) { // Error }
Можно слышать множество жалоб на распухание логики обработки ошибок до состояния, когда строго написанный вызов функции может занимать столько строк, что уже невозможно увидеть всю картину в целом. Как картостроители, мы знаем, что обрастание таким количеством Достойных стандартов кодирования, что невозможно написать замечательную программу – ошибочный путь.
В процедурном (и, в некоторой степени, объектном) подходе к коду ведется дискуссия по поводу стратегии обработки ошибок, где их следует обрабатывать. В дискуссии рассматриваются два подхода. Первый говорит, что вызываемая подпрограмма не должна возвращать управление, пока она не выполнила то, что ее просили, и это можно назвать "полным делегированием". Другой говорит, что вызываемая подпрограмма должна выполняться, пока не столкнется с проблемой, и тогда аккуратно возвращать наверх весь мусор, который она получила и сообщать вызывавшему, что произошла ошибка – это мы назовем "ложная готовность".
Привлекательность полного делегирования состоит в том, что при этом получается очень чистый код с вызывающей стороны, и он может быть очень эффективным, и он препоручает ответственность за поддержание состояния более низких уровней самим уровням. Недостаток заключается в том, что он работает только при условии, что вызывающей стороне на самом деле не требуется обработка последствий ошибки в ее собственном контексте. Это ограничивает его системами для типовых ситуаций, где приложение действительно может не знать о том, что происходит на нижних уровнях, и если нижний уровень реально не сможет устранить проблему, то попытка возврата будет недопустима, поскольку приведет либо к зависанию процесса либо к фатальной ошибке.
Ложная готовность всегда позволяет вызывающей стороне отреагировать на проблемы, и вложенные вызовы смогут откатить стек, пока не будет достигнут уровень, способный разобраться с проблемой. Логика обработки ошибок пронизывает каждый уровень, но может быть минимизирована аккуратным кодированием, если автор и сопровождающий знают, как работать в этой схеме. Кроме того, можно обеспечить трассировку вызовов, показывающую, как процесс напоролся на проблему, так что всегда можно воспроизвести ситуацию.
Мы не думаем, что нужно дискутировать на эту тему, поскольку когда полное делегирование применимо, оно оказывается на самом нижнем уровне. Смешивание этих подходов приводит к кошмару в коде, поскольку нарушает концептуальную целостность.
Некоторые объектные языки предоставляют исключения, которые позволяют автоматически схлопывать стек до уровня, ответственного за обработку данной ошибки. Это замечательный способ освободить основную логику от деталей обработки ошибок. Важный момент, который нужно помнить, заключается в том, что иногда исключение может быть передано гораздо выше, если ты хочешь не просто обработать его, а хочешь знать, из-за чего оно произошло. Сообщение об ошибке с нижнего уровня, говорящее
Could not write() datafile ftell() = 246810
если за ним не следует другое, говорящее
Could not Save World
при отладке просто бесполезно. Ты можешь передавать исключения на более высокий уровень без нарушения главной логики управления, и следует подумать, как это сделать.
Не злоупотребляй в рабочее время исключениями для создания замысловатого потока управления. В особенности не скрывай longjmp() в макросах и не вызывай их из обработчиков. Если ты желаешь поэкспериментировать с Силами Тьмы, делай это дома. Нам всем приходится это делать, но что печальнее всего, и наши коллеги могут ухватиться за неправильную идею и начнут ее рационализировать. Не кажется ли странным, что мы производим сегодня языки, которые страдают запорами по этому поводу, когда заняло годы просто правильно переопределить с помощью const описания прототипов функций, но при этом нам позволено фокусничать с потоком управления так, как мы не пытались делать даже на ассемблере?
По возможности избегайте расставлять захламляющие ваш код assert()и условную компиляцию отладочных макросов. Вам не достичь необходимой достаточности плато качества, если все вокруг завалено мусором.
Увлечение формой (а не содержанием) и комбинаторный взрыв
По некоторым причинам существует мнение, что для того, чтобы системы были робастными (устойчивыми к ошибкам), им требуются нормальные режимы, режимы сбоя, в которые они попадают при сбое, и режимы восстановления, в которые они переходят после попадания в режим сбоя для возврата в нормальный режим. Частично это провоцируется потерявшими ориентировку пользователями, которые пытаются описать цели в случае сбоя, но делают это рассуждая о «режимах» системы. Это деликатная область, поскольку при обсуждении сбоя пользователи должны думать о составляющих реальной системы, которые могут давать сбой, и они должны обсуждать сбои заранее, раз они вынуждены подписывать Требования Пользователя, которые потом могут быть использованы как палка, которой их будут бить. Это значит, что они должны пытаться изучить финальную реализацию лучше, чем ее знают сами разработчики, чтобы суметь описать, что нужно делать при сбое компонентов.
Подчеркивая важность диалога, необходимо также отметить часто упускаемый момент. Действительно ли пользователь хочет, чтобы вы реализовали режим сбоя, детально описанный в Требованиях Пользователя? Может будет достаточно системы, которая просто работает? Конечно, скорее всего так и есть, но многие команды сломя голову бегут и реализуют эти сбои, как и сказано в Требованиях Пользователя.
Современная легенда в ICL гласит, что когда они покупали первую партию плат от Fujitsu, то сделали оценку, что надежность будет составлять 1% отказов. Поэтому прямо перед отправкой первой сотни один из директоров Fujitsu взял сверху из ящика плату и, перед тем как положить ее обратно, стукнул по ней молотком.
Помимо необходимости управлять переключениями состояний и исполнением редко когда нужного кода, в системах такого рода есть более глубокая проблема.
Сначала мы находимся в нормальном режиме. Затем попадаем в режим сбоя. Затем в режим восстановления. Что случится, если опять произойдет сбой? Что, у нас приключился сбой во время восстановления из режима сбоя? Восстановления из сбоя во время восстановления из сбоя? Тут очень легко появляется необходимость бесконечного расползания системы режимов, а не просто распознавание сбоев. Конечно, если дизайн всех уровней одинаков, то ничего страшного – вам остается лишь доказать, что это именно тот случай.