Как написать ТЗ на простую программу (калькулятор)
Рассмотрим, как может выглядеть работа по созданию ТЗ на несложную программу.
Возьмём калькулятор, который будет выполнять базовые арифметические операции (+, -, *, /).
Работу можно построить в 2 этапа:
Поиск позитивных фактов о том, что именно мы хотим от калькулятора;
Поиск негативных фактов, что может пойти не так (в широком смысле слова) при работе как с программой, так и с ТЗ — и формулирование требований, им препятствующим.
Используя наше понимание предметной области, можно сформулировать такие позитивные факты:
Калькулятор должен выполнять арифметические операции.
По большому счёту, позитивные факты на этом кончаются, так как дальше почти все относится к сфере «очевидно, что… — да?, а мне не совсем очевидно».
Уровень сложности: I’m too young to die
Что может пойти не так:
Как насчёт тетрации?
Подстрахуемся и проверим, что правильно понимаем себе количество арифметических операций. Ан нет, их не 4 штуки, а минимум 10. Суперкорень нам вряд ли завтра пригодится, да и логарифм с простыми корнями, если мы про простую программу, тоже. Так что давайте явно укажем, о каких 4-х операциях речь:
Калькулятор должен выполнять 4 базовые арифметические операции: сложение, вычитание, умножение, деление.
(с точки зрения классики инженерии требований тут 4 требования в одном, но на данном этапе это не так важно)
Мы не указали интерфейс пользователя
Как мы понимаем сейчас, интерфейс вполне может быть как минимум голосовым, если не тактильным по Брайлю.
Поскольку мы пишем ТЗ на простую (и для разработки, и для пользователя) программу, то выберем именно графический интерфейс.
Калькулятор должен демонстрировать числа, с которыми происходят вычисления, в графическом пользовательском интерфейсе.
Сколько будет MCMXII + VII?
Далее, если копнуть чуть глубже — римские цифры по-своему хороши, но нам точно будет проще с привычными.
Калькулятор должен оперировать в пользовательском интерфейсе арабскими цифрами.
Направление чтения чисел
Как ни странно, но в современном мире есть люди, которые читают слова не только слева направо, но и в других направлениях. Числа обычно читают слева направо, но поскольку затевать этнографическое исследование по каждому вопросу мы не готовы, лучше подстраховаться:
Калькулятор должен оперировать в пользовательском интерфейсе числами, которые читаются слева направо.
Польская нотация
Те, кто учился на программиста, могут знать, что есть разные формы записей арифметических и логических выражений, и привычная нам школьно-вузовская математическая инфиксная форма не единственная.
Калькулятор должен позволять вводить и обрабатывать арифметические выражения в инфиксной записи.
So far, so good
Что у нас пока получилось:
Калькулятор должен выполнять 4 базовые арифметические операции: сложение, вычитание, умножение, деление.
Калькулятор должен демонстрировать числа, с которыми происходят вычисления, в графическом пользовательском интерфейсе.
Калькулятор должен оперировать в графическом пользовательском интерфейсе арабскими числами.
Калькулятор должен оперировать в графическом пользовательском интерфейсе числами, которые читаются слева направо.
Калькулятор должен позволять вводить и обрабатывать арифметические выражения в инфиксной записи.
Классифицируйте это немедленно
У любого хоть немного системного — … аналитика, … программиста или … администратора наверняка возникнет желание классифицировать этот перечень для удобства анализа и работы с ним. Как минимум мы чётко видим подмножество требований про пользовательский интерфейс:
Функциональные требования
Калькулятор должен выполнять 4 базовые арифметические операции: сложение, вычитание, умножение, деление.
Требования к интерфейсам
Калькулятор должен демонстрировать числа, с которыми происходят вычисления, в графическом пользовательском интерфейсе.
Калькулятор должен оперировать в пользовательском интерфейсе арабскими числами.
Калькулятор должен оперировать в пользовательском интерфейсе числами, которые читаются слева направо.
Калькулятор должен позволять вводить и обрабатывать арифметические выражения в инфиксной записи.
Хм, последнее требование содержит в себе и функции тоже, что вроде как делает его и функциональным требованием, так и требованием к интерфейсу. И это называется ТЗ на простую программу? Запутались в 3-х соснах, то бишь в 5 требованиях — захотели упростить себе дальнейшую жизнь классификацией, но стало только хуже — ну, типичное горе от ума! :)
Ладно, давайте вернёмся на классический вид структуры, который мы проскочили — иерархический, в духе функциональной декомпозиции. В нём можно под каждую функцию запихнуть как функциональное подтребование, так и любое другое. Любое другое можно для удобства и обобщения назвать «ограничение» (constraint), т.к. они только и делают, что ограничивают поведение функции и ничего более!
Заодно можно поправить порядок записи требований на более хронологический, похожий на сеанс работы с программой, если не её тестирование:
Калькулятор должен позволять вводить арифметические выражения.
> Калькулятор должен позволять вводить арифметические выражения в инфиксной записи.
Калькулятор должен демонстрировать числа, с которыми происходят вычисления, в графическом пользовательском интерфейсе.
> Калькулятор должен оперировать в пользовательском интерфейсе арабскими числами.
> Калькулятор должен оперировать в пользовательском интерфейсе числами, которые читаются слева направо.
Калькулятор должен выполнять 4 базовые арифметические операции: сложение, вычитание, умножение, деление.
> Калькулятор должен выполнять арифметические выражения в инфиксной записи.
Про инфиксную запись получилось 2 раза, что опять-таки не комильфо с точки зрения DRY, внесения изменений в программу, модульности и проч. Но тут уж как обычно с архитектурой (в нашем случае, со структурой требований) — приходится идти на компромиссы, т.к. не существует форм, одинаково подходящих для всего. Ну, и как ни странно, вводить выражения действительно можно в одном виде, хранить в другом, а выполнять в третьем — это было бы глупо, но технически возможно.
А вот это уже сценарий!
Поскольку у нас получился практически сценарий, а не просто иерархия, уже можно проверить его на замкнутость-полноту — позволит ли такой набор операций, выраженный функциями, получать нужный нам результат?
Как будто бы да, но человек со взглядом тестировщика легко подскажет, что позволять вводить, демонстрировать вводимое и вычислять результаты операций — это ещё не значит показывать результаты вычислений!
Поэтому давайте добавим последнюю явную функцию:
Калькулятор должен показывать результаты вычисления арифметических операций.
Отдельно заметим, как у нас тут плавает лексика. То мы пишем книжное «демонстрировать», то переходим на более неформальное «показывать». То «выполнять арифметические выражения» (м, а так можно вообще? выполняют вроде команды и запросы, а выражения, наверное, можно только обрабатывать), то «вычисления». В ходе разработки ТЗ такое полностью допустимо, т.к. мы хотим выражать смысл кратко, ясно и однозначно и для этого ищем и пробуем разные слова. Но при финализации требований будет полезно утрясти словарь и сделать его более однородным.
Фактически у нас получился типичный минимальный прото-юскейс из 4-х шагов:
Программа предоставляет возможности ввода исходных данных и выдачи команды
Пользователь вводит исходные данные и даёт команду
Программа выполняет запрошенную команду над данными
Программа сообщает пользователю результат выполнения команды
А интерфейс ввода?
Так, кажется мы что-то забыли. Пользователький графический интерфейс показывает цифры и операции, это ок. Но ведь для ввода чисел и команд тоже нужен интерфейс. И тоже не голосовой, но есть как минимум 2 варианта:
Клавиатурный ввод
Ввод мышью….
Можно затребовать оба интерфейса сразу, по крайней мере такой вариант мы видим в типичном десктопном калькуляторе. Стоп, а почему мы решили, что у нас десктоп, а не мобилка? Неужели для простейшей программы надо учитывать контекст применения? Получается, да. Или нет?
Калькулятор должен позволять вводить арифметические выражения посредством графического интерфейса.
Можно ли 7 шапок?
И вот так из вопроса про интерфейс ввода мы возвращаемся к более общему вопросу —, а для какой категории устройств нужен калькулятор? И кстати, что насчёт планшетов? (Слона-то мы и не заметили)
Кажется, тут можно схитрить и потребовать, чтобы программа была кроссплатформенной и адаптивной. Фух, гора с плеч…
Но стоп, постойте, на каких именно категориях устройств разработчик будет демонстрировать и сдавать программу? У Андроид-устройств, например, есть множество версий устройств, да ещё на разных поколениях ОС.
Если требовать абстрактной кроссплатформенности и адаптивности, то нельзя сказать, что мы делаем простую программу, т.к. в общем случае добиться таких качеств без ограничений на версии устройств и операционных систем нельзя.
Поэтому приходится оговаривать конкретные подклассы устройств и операционных систем. Если мы хотим простоты для разработчика и пользователя, разумно выбирать наиболее популярные конфигурации устройств на рынке.
Учитывая, что у настольного компьютера практически всегда минимум 2 интерфейса ввода (клавиатура и мышь), а на смартфоне всё-таки доминирует тачскрин, решаем делать программу именно для смартфона.
Итого, можно взять самое популярное устройство по итогам продаж за последний год в нашем регионе, например, Redmi 9A (32 ГБ).
Как будет выглядеть требование-ограничение для этого случая:
Калькулятор должен работать на устройствах Redmi 9A (32 Гб).
Шеф, всё сломалось
Вещи ломаются, софт тоже может сломаться или не сработать. Ожидать 100%-ной надёжности не приходится, важно выбрать какой-то разумный компромисс. В общем случае полезно делать надёжность, соответствующей ожиданиям пользователей. В случае сложной и дорогой программы стоит изучить показатели конкурентов и сделать расчёты стоимости отказа для пользователей и бизнеса.
В нашем же случае хватит простой прикидки — в скольки случаев из 100 программа может позволить себе сбой? У нас нет никакой дополнительной информации о контексте использования, поэтому можно заложить 1–2 сбоя на 100 команд.
Калькулятор должен выполнять вычисления над выражениями с надёжностью не меньше, чем 98%.
Поговорим о габаритах…
В физическом мире габариты и форм-фактор продукта, устройства сильно влияют на его потребительские качества.
В цифровом мире аналогом габаритов можно с некоторой натяжкой считать размер программы в памяти и на диске.
Представьте себе, что наша программа при скачивании весит 1 Гб. Допустимо ли такое? Вряд ли.
Тут можно опираться на benchmarking, т.е. измерение характеристик типичных программ такого класса на рынке. Или учитывать, в какой стране, с каким интернетом и на каких телефонах она будет применяться.
Однако например у Google Play есть собственные рекомендации и ограничения, которые можно напрямую использовать:
Калькулятор должен поставляться в виде дистрибутива размером не более 200 Мб.
… и эффективности
Следующий вопрос — нормально ли, что программа занимает 2 Гб оперативной памяти в телефоне в ходе применения? Для нашей программки тоже выглядит чересчур.
Если поискать информацию о лучших практиках, можно выйти на рекомендации, чтобы программа занимала в памяти не более 50 Мб. На этом и остановимся:
Калькулятор должен использовать не более 50 Мб оперативной памяти телефона.
Не тормози!
Рассмотрим ещё один не совсем очевидный, но вполне возможный случай негативного события — представим, что вводим в калькулятор 2 + 2 и калькулятор думает 14 часов. Ну или хотя бы 7 минут. Ладно, пусть будет 15 секунд. Что вы скажете о таком калькуляторе? Нужно ограничить это безумие!
И в вопросах скорости вычислений нам на помощь может прийти не только вездесущий бенчмаркинг, но и когнитивные эвристики Якоба Нильсена — например, про то, что реакция менее 0,1 секунды воспринимается большинством людей как мгновенная. Ну что же, так и запишем:
Калькулятор должен проводить вычисление арифметического выражения не более чем за 0,1 секунду.
Сколько вешать в граммах?
Если вернуться к вопросу вычислений, то можно вспомнить, что они проводятся с определённой точностью, не всегда абсолютной.
Опять-таки ищем лучшие практики для бытовых калькуляторов и выходим на уровень:
Калькулятор должен производить вычисления с точностью не хуже 8 знаков после запятой.
Знак бесконечности
С другой стороны, можно пытаться оперировать в вычислениях очень большими или очень маленькими числами. Сделать универсальное устройство не очень просто и скорей всего не нужно. Надо выбрать разумный диапазон пределов расчётов.
Поиск говорит, что типичный диапазон большинства бытовых калькуляторов составляет от 10^-99 до 10^99, однако для ввода чисел со степенью нужна возможность указывать степень отдельной командой, чего для простого калькулятора хочется избежать.
Кроме того, тут мы возвращаемся к вопросу про интерфейс —, а сколько цифр мы хотим одновременно видеть на экране? У обычного карманного калькулятора 8–10 цифр на экране. Тогда можно ограничиться диапазоном в 10 цифр — т.е. 10 млрд как максимальное число. Но этого не хватит даже, чтобы показать годовой бюджет России (18 трлн в 2024-м году).
Однако программный калькулятор отличается от физического тем, что он может управлять размером отображаемых чисел и вообще делать перенос строк. После некоторых экспериментов можно установить, что достаточно читаемыми могут быть числа, представленные в виде 5 строк по x 3 разряда x 7 троек нулей, те ~= 100 разрядов.
Таким образом, можно вернуться к диапазону типового калькулятора парой абзацев выше:
Калькулятор должен производить вычисления с числами от 10^-99 до 10^99.
Наши пальчики устали
Так, что у нас ещё осталось, какие риски, что может пойти не так?
Представим, что мы вводим выражение в виде длинной цепочки с большим количеством различных операций. Какое количество операций имеет смысл поддерживать?
Для компьютерной программы можно сказать, что всё равно — пока человек не устал вводить цифры и знаки, программа всё может обработать. Но значит ли это, что длина выражения может быть бесконечной? С практической точки зрения смысла в этом мало, поэтому и тут стоит договориться о точке, когда ваша программа наконец скажет ERROR….
Ой, кстати, а что насчёт ошибок, которые не являются сбоем? Например, деление на 0? Надо же как-то реагировать на это:
Калькулятор должен сообщать о невозможности выполнить операцию, если она математически невозможна.
Вот так добавление одной только операции «разделить» нам усложняет поведение программы.
Ну и заодно давайте опишем, что же делать со сбоями:
Калькулятор должен сообщать о сбое в ходе выполнения операции.
И так до самого конца сплошные черепахи
Я предполагаю, что прочитав до этого места, вы как минимум ужаснулись количеству вещей и аспектов, о которых стоит подумать при создании, казалось бы, простейшего программного калькулятора. И это мы ещё не касались вопросов:
— графического дизайна,
— какие кнопки будут у калькулятора, кроме цифровых и операций,
— скорости реакции калькулятора на нажатие кнопок:)
Хорошая новость в том, что в больших программах этих аспектов больше (например + информационная безопасность), но ненамного. Плюс о некоторых аспектах, которые я называю макро-ограничениями, нужно подумать и разобраться один раз на всю программу (ок — на подсистему, сервис, микросервис), а не для каждой её функции заново.
Кроме того, по мере накопления опыта вы можете сами себе сделать «калькуляторы» различных перечисленных выше характеристик программы, т.к. математика таких расчётов такая же элементарная, как и у нашего калькулятора тут.
Смотрим итоговую сборку требований
Давайте соберём и посмотрим, что же у нас в итоге получилось:
Функции и их микро-ограничения
Калькулятор должен позволять вводить арифметические выражения.
Калькулятор должен позволять вводить арифметические выражения посредством графического интерфейса.
Калькулятор должен позволять вводить арифметические выражения в инфиксной записи.
Калькулятор должен демонстрировать числа, с которыми происходят вычисления, в графическом пользовательском интерфейсе.
Калькулятор должен оперировать в пользовательском интерфейсе арабскими числами.
Калькулятор должен оперировать в пользовательском интерфейсе числами, которые читаются слева направо.
Калькулятор должен выполнять 4 базовые арифметические операции: сложение, вычитание, умножение, деление.
Калькулятор должен выполнять арифметические выражения в инфиксной записи.
Калькулятор должен показывать результаты вычисления арифметических операций.
Калькулятор должен сообщать о невозможности выполнить операцию, если она математически невозможна.
Калькулятор должен сообщать о сбое в ходе выполнения операции.
Макро-ограничения
Совместимость
Калькулятор должен работать на устройствах Redmi 9A (32 Гб).
Надёжность
Калькулятор должен выполнять вычисления над выражениями с надёжностью не меньше, чем 98%.
Эффективность
Калькулятор должен поставляться в виде дистрибутива размером не более 200 Мб.
Калькулятор должен использовать не более 50 Мб оперативной памяти телефона.
Производительность
Калькулятор должен проводить вычисление выражения не более чем за 0,1 секунду.
Точность
Калькулятор должен производить вычисления с точностью не хуже 8 знаков после запятой.
Границы вычислений
Калькулятор должен производить вычисления с числами от 10^-99 до 10^99.
Какие ещё дыры вы видите в такой спецификации требований? :)
За картинки спасибо нейрохудожнику Алине Богачёвой.