Терминология
Бывает непросто сопоставить то или иное понятие в одном языке со словом в другом. Смысл многих слов в той или иной мере зависит от контекста. Не является исключением и "великий и могучий". Поэтому перед тем как вообще что-нибудь начинать обсуждать, неплохо бы дать конкретное определение. Особенно это касается технической терминологии, в том числе и компьютерной. Английский язык, ставший стандартом де-факто в IT-индустрии, оказался удобен для обозначения компьютерных понятий, и не столько потому что индустрия исторически развивалась по большей части в США, а сколько в силу своих лингвистических особенностей.
Теоретическая база для информационных технологий появилась как таковая совсем не штатах. И даже не в старушке Англии. Скажем так это плод совокупных усилий ученых по всему миру. Да, в большей степени европейских, и не в малой степени британских, но за терминологией мог закрепиться вполне и какой-нибудь немецкий или русский. А может быть даже древнегреческий или латынь. Английский язык пришелся кстати из-за свойственной ему контекстной зависимости и многообразия отглагольных существительных.
Если в английском языке взять ряд слов и вставить их в определенный контекст легко можно получить совершенно новый семантический набор. Слово 'stable', взятое отдельно, означает всего лишь то, что нечто способно стоять. Применительно к разным вещам это у нас и устойчивость, и прочность, и твердость, и постоянство. Поэтому вы редко увидите тот или иной английский термин из одного слова, почти всегда это пара. С другой стороны у слова 'stable' существует и масса синонимов либо уточняющих, либо наоборот расширяющих понятие. Так, например, по отношению к программному обеспечению синоним 'tolerant' лучше соответствует русскому 'устойчивый'. 'Stable software' это скорее понятие относящееся больше к жизненному циклу программных продуктов. Обычно мы так и говорим - стабильная версия.
Это такое лирическое отступление. Итак, речь об устойчивом (tolerant, или даже fault tolerant) программном обеспечении. О том когда можно его таким называть, чем характеризуется, и как его таковым сделать.
Устойчивое ПО
В общем случае когда речь заходит об устойчивом программном обеспечении, то говорится о комплексе мер и технологий, позволяющих этому ПО работать в экстремальных условиях и высокоответственных системах. Это и разнообразные технологии избыточного дублирования, кластеризации, резервного копирования, и приемы обеспечения безопасности, в том числе защита от пресловутого человеческого фактора. Устойчивое ПО в общем смысле включает в себя гораздо более широкий спектр проблем чем то, о чем я могу более или менее смело рассуждать. И что бы не ступать на зыбкую почву своего невежества сразу отрежу все вышеперечисленное оставив лишь узкую полоску, относящуюся лишь к отдельной программе, библиотеке или даже классу и его методу.
Так же я не буду распространяться об устойчивости к злонамеренному употреблению этой самой программы. Защита от взлома это другая история и по отдельности какой-то модуль или функция, как правило не снабжается частью отвечающей за безопасность. "Защита от дурака" тоже отдельная тема, которую я частично уже разбирал в другой заметке цикла. Здесь речь об устойчивости к возникновению внутренних ошибок и ситуаций косвенно приводящих к выходу из строя как самой программы, так и окружения в котором она работает.
Условно устойчивость комплекса или отдельной программы исчисляется временем наработки на отказ. На программу сверху накладывается требование о количественной устойчивости в процентах. В зависимости от задачи, которую решает программа, это могут быть значения близкие к ста процентам. Условно говорится что программа доступна на 99.72% если она работала, к примеру, без одного дня год. Понятно, что такие замеры производятся уже в боевых условиях и не совсем корректно отражают действительность. Программа прекрасно может наделать бед и в оставшиеся 0.01% времени, или будет ровно на одну секунду вылетать 236 раз в сутки, что тоже вряд ли можно назвать устойчивостью.
Наработка на отказ вообще термин пришедший в программирование, да и в целом в информационные технологии из обыкновенных дедовских механических систем. Это и двигатели, и разнообразные приборы, узлы, агрегаты и все они вместе, начиная от подшипников и сервоприводов до самолетов и атомных реакторов. И этот термин, и сходный ему термин высокая доступность (high availability) призван характеризовать скорее комплексы разных информационных систем, чем отдельные её элементы. Что такое веб-сайт? Это, собственно, и сам веб-сайт - программа, и веб-сервер, и аппаратные средства, и каналы связи, и доменная система и много много прочего связанного воедино.
Отдельно же программу, отвечающую за отображение веб-страницы, при условной стопроцентной отказоустойчивости всего остального не очень удобно измерять в процентах надежности. Разумеется и сама программа может иметь встроенные механизмы обхода каких-то сбоев в окружающем мире. Ну например, в программе могут быть зашиты средства реагирования на отказ файловой системы, разрыв интернет соединения, ошибки пользователя. Однако их проще оценивать специально создавая такие ситуации, в которых программа должна работать, а не дожидаться когда это произойдет произвольно во время эксплуатации. Создание таких ситуаций искусственно в общем случае называется тестированием.
Таким образом, устойчивая программа это скорее та, которая проходит тесты. Причем не абы какие, а все. В том числе и нагрузочные. Соответственно, что бы показать что программа устойчива прежде всего нужен комплекс тестовых мероприятий, который в какой-то степени гарантирует надежность.
Тестирование
Под тестовыми мероприятиями может пониматься каждый раз разный комплекс мер. Хорошо если этот комплекс каким-то образом описан прямо в требованиях. Это, как я не раз об этом упоминал, документальное разграничение ответственности. Нельзя привлечь к ответственности программиста допустившего ошибку, если она просочилась сквозь предписанный комплекс тестов. Это уже будет недосмотром в первую очередь того, кто разрабатывал тесты, того кто подписывал акты приемки и тех кто работал над подготовкой технического задания и требований. Плохо то, что и тем, и другим, и даже третьим часто является сам программист.
Сколько бы изысканий не проводили разнообразные аналитики, и сколько бы раз результат прямо не говорил о том, что хотя бы разрабатывать и тестировать программы должны разные люди, воз фактически и ныне там. Позволить себе команду квалифицированных тестировщиков может даже не всякая компания, занимающаяся исключительно коммерческой разработкой. Что уж говорить о каком-то подразделении непрофильного предприятия. Хотя как-раз в них то часто разрабатываются подсистемы прямо влияющие на реальные процессы, в том числе и ядерные.
Вообще о последствиях ошибок в ПО говорится довольно часто. Свежий случай, опубликованный недавно, когда оказалось, что 39 человек осудили за то чего они не совершали. Один даже самоубился в результате. С другой стороны, обычно муссируются какие-то сильно нашумевшие истории, гораздо реже в прессу попадают мелкие происшествия. А таких, надо сказать, не мало и происходят они в основном как-раз в следствии ошибок в специализированном ПО, разработанном в конкретном предприятии для конкретных нужд, а далеко не из-за глюков операционных систем, как часто приходится слышать.
Взломы операционных систем или сетевого оборудования тоже ведут к немалым убыткам и серьезным катастрофам, но и борьба против хакеров и вирусов ведется серьезнейшая. А вот об ошибках штатных программистов на производствах, в медицине, в финансовых организациях и, что уж греха таить, в государственных учреждениях говорить как-то не принято и не интересно. Складывается впечатление как-будто это так и должно быть.
Возвращаясь к нашим баранам. Обеспечение качества это отдельная наука со своей спецификой, нюансами, технологиями и инструментами. Тезисно касательно ПО: во-первых, это разработка стратегии тестирования. Нет универсального чек-листа из методологий тестирования, на все случаи жизни. Одно дело тестирование того же веб-сайта и совсем другое - системы бортового компьютера марсохода. В результате разработки стратегии появляется перечень конкретных тестов, которыми нужно покрыть испытуемый образец ПО. Во-вторых тест нынче это тоже специальная программа создаваемая другим программистом - разработчиком тестов. Тесты призваны симулировать окружающую среду для программы. Ввод пользователя, показатели датчиков, сетевые подключения, файловую систему, базы данных и все прочие ресурсы задействованные в программе. В том числе и вероятные выходы из строя этих самых ресурсов.
На все тесты программа обязана выдать ожидаемую, желательно задокументированную, реакцию. То есть это должны быть не только правильные ответы на правильные вопросы, но и реакция на сбои, ошибки данных и прочие напасти. Если в тесте ожидается, что в результате сбоя файловой системы программа должна вылететь, она должна именно вылететь. Причем не абы как, а описанным способом с заранее обозначенным кодом завершения. Программа не должна обойти сбой, пропустить запись файла и продолжить работу как ни в чем ни бывало.
В системах где не не последнее значение имеет время реакции так же измеряется и время в тех или иных ситуациях. Веб-страница выполняющая загрузку какого-то тяжелого скрипта более чем несколько секунд не должна просто так "висеть" в браузере, а, скажем, должна показывать какой-нибудь мультик. Игра стрелялка застревающая внутри локации тоже вряд ли понравится пользователям. Не говоря уж о системах реального времени, где мгновенная реакция это смысл самой программы.
В связи с развитием методик тестирования возникла даже такая специальная технология разработки - экстремальное программирование. Это когда создается сперва как-раз набор тестов, а под них пишется собственно сама программа. Такой подход оправдан в некоторых случаях, в основном в коммерческих разработках, когда есть четкие и жесткие требования. Однако, это идеалистическое скорее направление - путь в котором следует двигаться - а не реально применимая технология для большинства ситуаций.
Приемчики
Раз уж предмет разговора сужен до отдельных программ и до отдельных ситуаций, то и приемов так или иначе делающих программу устойчивой не так уж и много. Стандартный прием это обыкновенная обработка ошибок. Для большинства современных языков программирования это конструкции обработки исключений (try... catch...). О них как в общем случае, так и применительно к конкретным языкам говорится часто и много. Конструкция не особенно хитрая, прием в целом понятен и довольно прямолинейно применяется.
Единственное, что стоит отметить про отлов исключений это то ,что нередко случается перебор, и применяют эти конструкции в местах для этого не совсем подходящих. Доходит до того, что его начинают использовать полностью вместо условных операторов и даже неким образом в подобие GOTO конструкций. Этим страдают видимо те, кто внезапно обнаружил такую "фичу" в их любимом языке и лучше всех "понял" ее "скрытые" возможности. Ну или просто в целях повыделываться. Обработку исключений следует применять только для обработки исключений.
Немного запутаннее и сложнее ситуация обстоит в тех случаях, когда исключения должны выполнять не только некую отчетную функцию, но и нести какую-то полезную нагрузку. Когда какие-то аварийные ситуации должны приводить не к завершению программы, а к некому запасному варианту. Если обозначенные ранее тесты требуют от программы продолжать работу невзирая на то что программа, например, не смогла записать что-то в журнал работы. Программа должна обработать исключение, переключиться на работу без файловой системы, и, например, продолжить журналирование в сетевое подключение или в консоль.
Таких запасных вариантов может быть и несколько. Или, предположим, пропало интернет соединение, от которого программа ждет какие-то важные данные, без которых продолжение работы невозможно или бессмысленно. В одних случаях завершение работы с ошибкой это приемлемое поведение. В других - программа должна, скажем, либо подождать энное количество времени восстановления подключения, или самостоятельно сделать определенное количество попыток перезапустить подключение.
Если все же стало понятно, что все пропало и придется вываливаться, то устойчивая к сбоям программа должна корректно завершить какие-то ранее запущенные процессы. Это могут быть те же открытые файлы, транзакции базы данных, уведомление ответственных лиц о непредвиденном сбое, передача управления пилоту, аварийная остановка ядерного реактора, или запуск процедуры самоуничтожения.
В любом из описанных случаев обработки исключения могут понадобится дополнительно и так называемые сейвпоинты, ну или говоря по-русски точки сохранения. Это тоже отдельный прием обеспечить программе устойчивость к сбоям в целом и, что важнее, сохранить целостность данных в логике программы. Сейвпоинтами могут выступать те же транзакции в БД или специальные структуры, т.н. хранители (memento).
Последнее, кстати, возвращает нас к предыдущей заметке об историчности в базах данных. Историчность, сохранение промежуточных состояний и откат к надежным данным в случае сбоя это в принципе общий подход подразумевающий под собой избыточность в данных (redundancy). Еще один вариант использования избыточности это так называемые CRC (cyclic redundancy code) проверки и вообще любое использование разнообразных контрольных сумм для гарантирования целостности данных из ненадежных источников.
Кроме избыточности в данных для увеличения надежности программы используется избыточность и посредством самого алгоритма. Это третий чуть менее распространенный прием. Он несколько сложнее в реализации и требуется в довольно специфических задачах. Это когда программа либо сама запускается многократно, либо внутри себя разветвляет потоки потенциально проблемных операций, и работает при этом с одним и тем же клиентским запросом. Затем несколько полученных результатов, если они не одинаковые, сравниваются отдельным специальным алгоритмом который, в итоге выбирает из них верный или приемлемый.
Существуют и еще более изощренные и специфические способы повышения надежности программ, но с ними мне лично сталкиваться не приходилось и поэтому можно поставить здесь жульническое "и т.д.". Тем более что дальнейшее повышение надежности отдельных программ какими-то более хитроумными средствами в среднем обходится уже дороже чем описанные в начале внешние способы повышения надежности всего комплекса в целом. Даже третий приведенный прием это что-то уже из области экстрима. Мне, например, это понадобилось единственный раз для разбора большого и хитровыдуманного XML файла прямо из интернета. Да и то больше в качестве эксперимента, чем из острой необходимости.
Баланс
В качестве заключения, не лишним будет повториться, что даже надежность хороша в меру. Если требование к какой-то запредельной устойчивости задано изначально, то не плохо бы удостовериться в его обоснованности. Бывает так, что надежность обозначена просто ради важности. В таких случаях советую просто и доступно, опираясь на все вышесказанное, объяснить во сколько обойдется эта самая надежность в денежном выражении. Уверяю, в большинстве случаев окажется, что и не нужна она такая вовсе. А если и нужна, то дешевле и проще обеспечить надежность не в программе, а, скажем, поставить в сервер рейд массив или подвести резервное сетевое подключение.
Идеальное тестирование тоже на самом деле эдакая асимптота, чем реально достижимое явление. Поэтому слишком глубоко уходить в постановку задачи таким образом, что все должно гарантировать тестирование и документация я бы не стал. По крайней мере не при настоящем положении дел. Особенно, если эти самые тесты будете делать вы сами (что, еще раз, полная бессмыслица). С другой стороны, стремиться к идеалу надо и когда-то технологии обеспечения качества достигнут таких вершин, что программистам больше никогда не понадобится домысливать самостоятельно что и в какой ситуации должна получать программа. А возможно и вообще не смотреть в скучное и непонятное техзадание.
*/
End;