В своей знаменитой статье, опубликованной в 1987 году, Фред Брукс утверждает: «Нет ни одного открытия ни в технологиях, ни в методах управления, "в одиночку" увеличивающего хотя бы на порядок производительность, надежность и простоту разработки программного обеспечения».
Брукс предсказал, что в области создания программ какая-либо отдельная методика или идея не сможет победить «монстра проваленных графиков, раздувшихся бюджетов и неработающих продуктов» [1]. Сегодня мы видим, что он был прав.
Мы не утверждаем, что разработчики нашли или найдут серебряную пулю, которая будет способна убить огромного многоликого монстра, олицетворяющего все кошмары разработки программного обеспечения. Мы лишь говорим, что, тщательно прицелившись, можно застрелить нескольких маленьких монстров имеющимися серебряными пулями. Это не решит всех проблем создания программного обеспечения, но позволит справиться с некоторыми из них, и такое последовательное продвижение, безусловно, сыграет положительную роль. А поскольку для маленьких монстров серебряные пули уже существуют, возникает вопрос: почему они все еще издеваются над нами?
Как заслужить доверие
Разработчики давно обсуждают, как определять степень доверия программам. Известны легионы идей, нацеленных на повышение их качества. Среди наиболее известных можно упомянуть минимальный стандарт на испытания, оценку качества программного обеспечения сторонними организациями и объективное обсуждение вопроса о «достаточно хорошем» программном обеспечении пользователями и разработчиками. Ни одна из этих идей не была воспринята сообществом разработчиков — несмотря на длительную неудовлетворенность степенью доверия программному обеспечению.
Сэм Канер освещает юридические аспекты разработки программного обеспечения в США. Составленный им Билль о правах покупателя программных продуктов включает в себя десять пунктов, два из которых мы процитируем.
Обнародуйте известные дефекты. Софтверная компания или поставщик услуг должны уведомить потенциальных клиентов об известных дефектах, выбрав для этого способ, понятный среднему потребителю продукта или услуги.
Массовые потребители имеют право критиковать продукт, использовать его в соответствии с законом и публиковать результаты сравнительных испытаний. Некоторые лицензии на программное обеспечение попросту запрещают клиенту публично критиковать продукт, обнародовать его сравнения с другими продуктами, использовать ?мгновенные снимки? экранов либо графические элементы для высмеивания или унижения продукта или выпустившей его компании. Между тем, Акт об авторском праве США дает право воспроизводить часть защищенной этим правом работы с целью критики, комментариев, преподавания и т.д. Создателям программного обеспечения нельзя позволить посредством «лицензионных» контрактов лишить массовых потребителей свободы слова, защищенные законами.
Эти предложения, при условии контроля за их соблюдением, могут обеспечить получение потребителями подробной информации о качестве конкретных программных продуктов. Проблемой реализации первого из них может стать создание поставщиками препятствий для выявления и обнародования дефектов. Это не значит, что дефекты останутся нераскрытыми, просто широкая публика узнает о них не слишком скоро. А если разработчики все же не хотят откровенничать по поводу выявленных дефектов, пусть устраняют те, которые можно устранить, а затем объявляют об этом.
Второе предложение более чем актуально, поскольку законодательство выводит разработчиков из-под удара критики. Другие отрасли подобных льгот не имеют, и для программных продуктов массового назначения тоже не следует создавать тепличных условий.
Другими словами, индустрия программного обеспечения способна идентифицировать маленьких монстров, для которых есть серебряные пули, а разработчики могут выгнать этих монстров из своих продуктов.
Пули для трех маленьких монстров
Валдис Берзинс [4] обещает скорую автоматизацию методов, которые позволяют устанавливать ряд характеристик программного обеспечения, важных для его качества. Он отмечает, что вероятность автоматического определения всех таких характеристик не слишком велика, но на пути к совершенству вполне разумно использовать уже существующие технологии. Берзинс перечисляет двенадцать «маленьких монстров», но мы сосредоточимся на трех из них: утечки памяти, переполнение буферов и файлы, оставшиеся открытыми после завершения работы программы. В [5] перечисляются конкретные методы анализа исходного кода с целью выявления этих проблем.
Некоторые из решенных или разрешимых проблем могут быстро воплотиться в технические решения. Они станут основой процесса сертификации, удостоверяющего, что программное обеспечение не откажет из-за этих проблем. Поставщики смогут самостоятельно верифицировать программное обеспечение, включать результаты его проверки в комплект поставки заказных продуктов или указывать их в маркировке массового программного обеспечения; проверка может осуществляться и независимыми организациями. Маркировка программного обеспечения когда-нибудь будет содержать (по традиции или в соответствии с законодательством) точную информацию о его характеристиках и качестве.
Утечки памяти
Утечкой памяти называется ситуация, в которой программа не освобождает выделенную динамическую память после отпадения в ней необходимости. Со временем накопление напрасно занятой памяти может привести к отказу системы. Утечки памяти — это ошибки программирования; следовательно, перед сдачей программного продукта разработчики должны их обнаружить и устранить.
Для выявления утечек памяти существует ряд методов и инструментов. Авторы [6] представляют «инфраструктуру для анализа указателей, которая обнаруживает утечки памяти, статически анализируя поведение программ». Они утверждают, что предлагаемый подход является новым, поскольку проверка осуществляется путем символьного моделирования структур данных и операций с ними в динамически распределяемой области памяти. Используя предложенный метод, разработчики могут статически обнаруживать утечки памяти без выполнения кода.
В [7] рассматривается статический инструмент анализа, позволяющий автоматически обнаруживать утечки памяти и удалять «зависшие» указатели в больших программах на Си и C++. Представлена система типов, в которой на каждый объект имеется лишь один указатель. Ее авторы утверждают, что их методика предотвращает утечки памяти в программах. В [8] также предлагается «вводить в программы дополнительный код для контроля над правильностью их поведения и восстановления после ошибок».
Для выявления утечек памяти окажутся полезными и еще несколько инструментов.
Инструменты общего назначения. В [9] обсуждается девять таких инструментов — mtrace, dmalloc, memwatch, ccmalloc, NJAMD (отладчик malloc с расширенными возможностями), YAMD (еще один отладчик malloc), Valgrind, mpatrol и Parasoft Insure++. В [10] рассматривается еще три: Checker 0.9.9.1, Electric Fence 2.0.5 и Mem-Test 0.10.
Diduce. Этот инструмент идентификации ошибок использует динамические инварианты [11].
CMC. Инструмент С Model Checker позволяет проверить реальный код без разработки программистом модели [12].
CCured. Система типов CCured работает с языком Си, помогая обнаруживать программные ошибки [13].
LCLint. Этот инструмент проверки моделей является модификацией программы Larch, разработанной в Массачусетском технологическом институте. Под новым названием Splint его продолжает развивать группа Inexpensive Program Analysis Group в университете штата Вирджиния [14].
Archer. Данный инструмент ориентирован на проверку доступа к памяти [15].
Таким образом, для борьбы с утечками памяти в программах существуют разнообразные методы и инструменты. Мы со всей определенностью можем утверждать, что перед выпуском коммерческих программных продуктов разработчики могут и должны избавиться от этих монстров. Чем больше утечек памяти им удастся устранить, тем выше будет надежность программного обеспечения и тем большего доверия оно заслужит.
Переполнение буферов
Такое происходит, когда размер выделенного программистом буфера оказывается недостаточным для вмещения всех копируемых в него данных. Это ставит под угрозу содержимое памяти вне буферной зоны и может привести к ошибкам при выполнении программы и к нарушению безопасности (используя переполнение буфера, хакер способен получить контроль над системой).
Однако маленького монстра переполнения буфера вполне можно победить, и методам выявления этой ошибки посвящен целый ряд работ. Предлагаемая в [16] методика обнаружения ошибок использует «беспорядочно искаженные потоки данных». Подобно ей, методика [17] основана на анализе ошибок, намеренно введенных в программу. В [18] путем проверки сигнатур базового блока удостоверяют, что выполняемая команда не имеет злонамеренного характера. Авторы [19] предлагают использовать наборы команд, для успешного выполнения которых процессор должен иметь ключ алгоритма рандомизации. А в [20] перечисляются статические и динамические методы выявления ситуаций переполнения буфера.
Незакрытые файлы
Проблема возникает, когда программа открывает файл F в ходе выполнения модуля X, причем действие модуля X завершается без ошибок, а файл F неумышленно остается открытым. Незакрытые файлы могут привести к порче данных и к ошибкам времени выполнения. Конечно, некоторые модули специально не закрывают файлы, чтобы впоследствии другие модули могли получить к ним доступ, но наш монстр не имеет отношения к таким ситуациям. В идеале серебряной пулей для него станет инструмент статического анализа, который обнаружит проблему без специальных дополнений в исходном коде. Отказ от проверок во время выполнения программы повышает эффективность исполняемого кода, а отсутствие необходимости в дополнениях упрощает процесс разработки.
Статический анализ может быть выполнен для программ, написанных на популярных языках. Например, в Microsoft разработали инструмент Slam [21], который проверяет характеристики программы на Си при помощи правил, записанных на языке SLIC (Specification Language for Interface Checking, язык спецификаций для проверки интерфейса). Правила SLIC представляют собой текстовые описания операций открытия и закрытия файлов на атомарном уровне.
Программисту не обязательно каждый раз создавать новые правила SLIC. Организация-разработчик может определить набор таких правил при установке Slam и по умолчанию использовать их в любом проекте. Однажды написанное правило SLIC будет применяться в системе Slam при анализе произвольной программы на Си. Для борьбы с незакрытыми файлами нужно ввести правило SLIC, требующее, чтобы модуль закрыл все открытые файлы перед окончанием работы. Slam может исследовать любую отдельную программу на Си, гарантируя, что все открытые файлы будут закрыты до ее завершения. Этот инструмент основан на системе ESP [22], которая впервые позволила осуществить верификацию программ за полиномиальное время с учетом путей выполнения. Альтернативный подход основан на классификации типов [23].
Итак, решение проблемы незакрытых файлов существует. Мало того, для этого не нужны ни непомерные расходы на программирование, ни затраты машинного времени.
Что дальше?
Рассмотренные предложения достаточно скромны по сравнению с грядущими качественными усовершенствованиями. В 2003 году Тони Хоар предложил грандиозную задачу разработки верифицирующего компилятора, который будет использовать математические рассуждения для проверки правильности компилируемых программ. Эта идея значительно более амбициозна, чем обсуждавшиеся здесь скромные и относительно недорогие шаги. Вот только совершить их разработчики могут прямо сейчас, и добиться успеха в самом ближайшем будущем. А отказаться от них — значит, потерять доверие пользователей к компьютерной отрасли и уважение ИТ-профессионалов.
- F. Brooks, No Silver Bullet: Essence and Accidents of Software Engineering. Computer, April 1987.
- L. Morris, Advice on Structuring Compilers and Proving Them Correct. Proc. 1st ACM Symp. Principles of Programming Languages, ACM Press, 1973.
- D. Parnas, A. John van Schouwen, Shu Po Kwan, Evaluation of Safety-Critical Software. Comm. ACM, June 1990.
- Trustworthiness as Risk Abatemerit. Proc. Center for Nat?l Software Studies Workshop on Trustworthy Software, Naval Postgraduate School, 2004.
- S. Hellem, D. Park, D. Engler, Uprooting Software Defects at the Source. Queue, Nov. 2003.
- Symbolic Pointer Analysis for Detecting Memory Leaks. Proc. 2000 ACM Workshop on Partial Evaluation and Semantics-Based Program Manipulation. ACM Press, 1999.
- A Practical Flow-Sensitive and Context-Sensitive С and C++ Memory Leak Detector. Proc. 2003 Conf. Programming Language Design and Implementation. ACM Press, 2003.
- Enhancing Software Reliability With Speculative Threads. Proc. 10th Int?l Conf. Architectural Support for Programming Languages and Operating Systems. ACM Press, 2002.
- Memory Leak Detection in Embedded Systems. Linux Journal, Sept. 2002; Memory Leak Detection in C++ . Linux Journal, June 2003.
- Memory Access Error Checkers. Linux Journal, May 1999.
- S. Hangal, M. Lam, Tracking Down Software Bugs Using Automatic Anomaly Detection. Proc. 24th Int?l Conf. Software Eng. IEEE CS Press, 2002.
- M. Musuvathi et al, CMC: Pragmatic Approach to Model Checking Real Code. ACM SIGOPS Operating Systems Rev., Winter 2002.
- G. Necula, S. McPeak, W. Weimer, CCured: Type-Safe Retrofitting of Legacy Code. Proc. 29th ACM Symp. Principles of Programming Languages. ACM Press, 2002.
- D. Evans, Static Detection of Dynamic Memory Errors. Proc. SIGPLAN 1996 Conf. Programming Language Design and Implementation, ACM Press, 1996.
- Y. Xie, A. Chou, D. Engler, ARCHER: Using Symbolic, Path-Sensitive Analysis to Detect Memory Access Errors. Proc. 9th European Software Eng. Conf. ACM Press, 2003.
- A. Jorgensen, Testing With Hostile Data Streams. SIGSOFT Software Eng. Notes. March 2003.
- Anup Ghosh, Jeffrey Voas. Inoculating Software for Survivability. Comm. ACM, July 1999.
- M. Milenkovic, A. Milenkovic, E. Jovanov, A Framework for Trusted Instruction Execution Via Basic Block Signature Verification. Proc. 42nd Ann. ACM Southeast Regional Conf., ACM Press, 2004.
- G. Kc, A. Keromytis, V. Prevelakis, Counter Code-Injection Attacks With Instruction-Set Randomization. Proc. 10th ACM Conf. Computer and Comm. Security, ACM Press, 2003.
- V. Ganapathy et al, Buffer Overrun Detection Using Linear Programming and Static Analysis. Proc. 10th ACM Conf. Computer and Comm. Security, ACM Press, 2003.
- J. Larus et al, Righting Software. IEEE Software, May-June 2004.
- Manuvir Das, Sorin Lerner, Mark Seigle, ESP: Path-Sensitive Program Verification in Polynomial Time. Proc. ACM Conf. Programming Language Design and Implementation, 2002.
- Corneliu Popeea, Wei-Ngan Chin, Type System for Resource Protocol Verification and Its Correctness Proof. Proc. 2004 ACM Symp. Partial Evaluation and Semantics-Based Program Manipulation.
Дэвид Ларсон (larson.david@uis.edu) — доцент факультета информационных управляющих систем, Кейт Миллер (miller.keith@uis.edu) — профессор отделения информатики в Университете штата Иллинойс.
Программы, заслуживающие доверия
В 1973 году Ф. Локвуд Моррис [2] воспользовался выражением «заслуживающее доверия программное обеспечение» (trustworthy software) при обсуждении доказательства правильности компиляторов, приписав его Джону Маккарти. Сегодня часто цитируют работу Дэвида Парнаса, вышедшую в 1990 году: «Мы полагаем, что продукт заслуживает доверия, если считаем, что вероятность наличия в нем изъяна, потенциально ведущего к катастрофе, приемлемо низка» [3]. Это определение тесно связано со следующим определением: «Программное обеспечение считается безопасным, если исключено (или, по крайней мере, весьма маловероятно), что результаты его работы приведут к катастрофе управляемой им системы».
В обоих определениях используется слово «катастрофа». Главное различие между ними и обычным определением надежности как вероятности безотказной работы в течение заданного времени состоит в том, что в последнем отказы не делятся на простые и катастрофические. «Катастрофа» не является техническим показателем, поддающимся количественной оценке. Одно и то же событие может для кого-то стать катастрофой, а для кого-то — не стать. Потеря неопубликованной рукописи, например, окажется катастрофой лишь для ее автора. Мера доверия, по определению Парнаса, требует установить, насколько гибельным будет отказ для пользователя или для разных групп пользователей. Мы понимаем под катастрофой неожиданный существенный вред, причиненный человеку по вине компьютерной программы.
Трудности измерения могут привести к выводу, что вопрос о доверии к программному обеспечению должен решать рынок. Другими словами, потребители стремятся покупать программное обеспечение с разумным сочетанием качества, цены и функциональных возможностей. Мы не согласны с данным выводом, поскольку внешние силы искажают рынок так, что качественное программное обеспечение кажется не столь привлекательным, каким оно является, и наоборот. Крупные корпорации склонны покупать программное обеспечение у других больших корпораций. Кроме того, на рынке программных продуктов их доля играет более важную роль, чем в других отраслях. Наконец, рынок часто не предоставляет адекватной информации о качестве, позволяющей потребителям принимать обоснованные решения.
David Larson, Keith Miller. Silver Bullets for Little Monsters: Making Software More Trustworthy, IEEE IT Pro, March/April 2005. IEEE Computer Society, 2005, All rights reserved. Reprinted with permission.