Крупные проекты не появляются на пустом месте. Написать в самом деле большую и сложную программу сразу не получится. Однако выход есть!
Разделяй и повторяй
Лучше всего это правило продемонстрировать на примере. Представим, вы хотите написать программу для решения головоломки Судоку. Чтобы навёл камерой на журнал Судоку и сразу увидел ответ. Круто? Было бы классно.
Как написать такую программу?
Сначала нужно разделить её на множество подзадач как технических, так и алгоритмических:
- как получить фото с камеры?
- как его сделать чёрно-белым?
- как выделить область с Судоку?
- как распознать цифры?
- как решить головоломку?
- как показать результат?
- как запустить это всё на телефоне?
Далее необходимо в «лабораторных» условиях разобраться с каждым пунктом. Это значит – найти способ решения каждой подзадачи, хорошенько «погуглить», сделать тестовый проектик и реализовать указанный функционал по принципу «лишь бы заработало»: только для того, чтобы поставить галочку, что ты понял, как это делается и что у тебя есть рабочий пример .
Как создать СЛОЖНУЮ программу в блокноте
Следующий этап
Когда все части реализованы, начинается самое интересное — создание полной программы . Не нужно ставить перед собой задачу сделать суперправильное архитектурное решение. Просто пиши, чтобы работало:
- вызывай модуль фотографирования, делай фотку чёрно-белой;
- находи область задачки, запускай распознавание;
- вычисляй решение и показывай его на экране.
На данном этапе не стоит отвлекаться на оптимизацию, проверку данных, универсальность. Твоя задача – сделать так, чтобы программа могла отработать от начала до конца под твоим контролем .
Что дальше?
Когда всё это получится, тогда можно сделать «Reset» и начать создавать программу «с нуля», имея в запасе функционал для решения каждой подзадачи и опыт их совмещения.
Опять же, не стоит слишком заморачиваться на универсальности и на оптимизации. Впрочем, вы и сами почувствуете, как лучше начать писать эту программу с определённой долей объектно-ориентированности и удобства использования.
Когда и это будет завершено, вы наконец-таки поймёте, как программа на самом деле должна работать. И вот теперь можно и в самом деле начать её создавать. Да, снова «с нуля», используя все былые наработки.
Крупные проекты не создаются на пустом месте
Необходимо сделать несколько итераций : от эскиза до прототипа и рабочего продукта. На каждой итерации продукт будет получаться более качественным и эффективным . Может, не всегда новая версия будет лучше прежней. Но при желании можно вернуться и к прошлой версии, ведь так?
Зачем начинать сначала?
Чтобы избежать «монстров» и «колоссов», которые могут обрушиться, когда ещё неясно, как программа работает. Программисты любят создавать своё, а не разбираться в старом. И это прекрасно, если только позволяет бюджет. Разделяйте и повторяйте, и будет тебе счастье!
Как создать программу «КАЛЬКУЛЯТОР» в блокноте
А как вы создаёте сложные программы? Пишите в комментариях!
Евгений Волосатов, Senior Developer с 20-летним опытом разработки различных проектов и преподаватель курсов «Backend-разработчик на PHP» и «Разработчик игр на C#» в OTUS
Материал подготовлен для студентов курса «Разработчик игр на C#» в образовательном проекте OTUS. Чтобы присоединиться к ближайшей группе, обязательно пройдите вступительное тестирование:
Источник: dzen.ru
Как одному написать сложную программу?
Крупные проекты не появляются на пустом месте. Написать в самом деле большую и сложную программу сразу не получится. Однако выход есть!
Разделяй и повторяй
Лучше всего это правило продемонстрировать на примере. Представим, вы хотите написать программу для решения головоломки Судоку. Чтобы навёл камерой на журнал Судоку и сразу увидел ответ. Круто? Было бы классно.
Как написать такую программу?
Сначала нужно разделить её на множество подзадач как технических, так и алгоритмических: — как получить фото с камеры? — как его сделать чёрно-белым? — как выделить область с Судоку? — как распознать цифры? — как решить головоломку? — как показать результат? — как запустить это всё на телефоне?
Далее необходимо в «лабораторных» условиях разобраться с каждым пунктом. Это значит – найти способ решения каждой подзадачи, хорошенько «погуглить», сделать тестовый проектик и реализовать указанный функционал по принципу «лишь бы заработало»: только для того, чтобы поставить галочку, что ты понял, как это делается и что у тебя есть рабочий пример.
Следующий этап
Когда все части реализованы, начинается самое интересное — создание полной программы. Не нужно ставить перед собой задачу сделать суперправильное архитектурное решение. Просто пиши, чтобы работало: — вызывай модуль фотографирования, делай фотку чёрно-белой; — находи область задачки, запускай распознавание; — вычисляй решение и показывай его на экране.
На данном этапе не стоит отвлекаться на оптимизацию, проверку данных, универсальность. Твоя задача – сделать так, чтобы программа могла отработать от начала до конца под твоим контролем.
Что дальше?
Когда всё это получится, тогда можно сделать «Reset» и начать создавать программу «с нуля», имея в запасе функционал для решения каждой подзадачи и опыт их совмещения.
Опять же, не стоит слишком заморачиваться на универсальности и на оптимизации. Впрочем, вы и сами почувствуете, как лучше начать писать эту программу с определённой долей объектно-ориентированности и удобства использования.
Когда и это будет завершено, вы наконец-таки поймёте, как программа на самом деле должна работать. И вот теперь можно и в самом деле начать её создавать. Да, снова «с нуля», используя все былые наработки.
Крупные проекты не создаются на пустом месте
Необходимо сделать несколько итераций: от эскиза до прототипа и рабочего продукта. На каждой итерации продукт будет получаться более качественным и эффективным. Может, не всегда новая версия будет лучше прежней. Но при желании можно вернуться и к прошлой версии, ведь так?
Зачем начинать сначала?
Чтобы избежать «монстров» и «колоссов», которые могут обрушиться, когда ещё неясно, как программа работает. Программисты любят создавать своё, а не разбираться в старом. И это прекрасно, если только позволяет бюджет. Разделяйте и повторяйте, и будет тебе счастье!
А как вы создаёте сложные программы? Пишите в комментариях!
Источник: otus.ru
10 лет практики. Часть 1: построение программы
Десять лет я пишу на С++. Первые пять лет моей задачей было писать эффективный код для компьютерных игр, потом основным требованием была стабильность, так как речь шла об автоматизации промышленных объектов. Я бы хотел поделиться своим личным опытом и практическими наработками, которые помогут создавать эффективные и в то же время стабильно работающие программы.
Материал разбит на части, от самых общих практических правил, интересных начинающим, до конкретных вопросов, актуальных опытным программистам.
В первой части я на практике дам свой ответ на самые базовые вопросы. Как вообще писать программу, в особенности — сложную программу? Что следует сделать в самом начале, чтобы потом не переделывать с нуля? Как создать оптимальную структуру программы?
Итак, коллеги, мы пишем на С++. Как правило, это значит что наши программы сложны. Некоторые — очень сложны. Чтобы программа работала как нужно мне, её создателю, требуется держать в уме то как она работает, вплоть до мельчайших деталей. Это необходимое условие, так как всё что не удалось полностью продумать — это гарантированные баги.
Удержать в голове всю картину на всех уровнях абстракции одновременно, невозможно. Поэтому я делю сложную систему на куски попроще. Каждый из них хорошо понятен и очевиден, как гвоздь. Всё что сложнее, собирается из более простых элементов. Очевидно? Конечно!
Но выполнять это правило нужно неукоснительно.
Если я пишу программу, которая использует неотработанную технологию или элемент, я сначала делаю этюд. Небольшую консольную программку, которая содержит в себе нужный кусок. Этот этюд я мучаю до посинения, пока чётко не пойму как и чего работает, какие ограничения на вводные данные, какие особенности и подводные камни. Да, у меня целая директория из нескольких сотен этюдов. Каждый в конечном итоге доводится до независимого куска кода (чаще всего, класса или связки классов), который я вставляю в программу как батарейку: вытащил старую версию — воткнул новую.
«Склейка» элементов иногда представляет собой довольно сложный механизм сама по себе, а потому требует отдельного этюда. Но этого мало, когда речь идёт о сложных механизмах. Все сложные алгоритмы я записываю на бумагу. Невозможно написать на С++ то, что не можешь описать простым русским языком. Когда описываешь что-то словами, мозг натыкается на подводные камни и нестыковки.
Они становятся видны в самом начале, а не в конце, когда код уже написан. Сам текст должен быть отшлифован, тонкие места — упомянуты. Ещё плюс: можно вернуться к проекту через год и не тратить недели на вспоминание почему это сделано именно так.
Для каждого заказчика я завёл по тетради. В них — все нетривиальные моменты, от создания реалистичной пены вокруг парусного фрегата, до вычисления оптимальных траекторий подъёмных кранов на узком пятачке вокруг раскалённых металлов и кислот. Если мне встречается похожая задача, я просто заглядываю в тетрадь. Там, среди перечёркнутых текстов и схемок, за 10-15 минут я воспроизвожу в памяти весь ход создания алгоритма со всеми его особенностями. Как правило, задачи не повторяются полностью, но какая-то идея оказывается нужной через три-четыре года.
Очевидно? Отлично! Чуть усложним и заглянем внутрь элементов, о которых я говорил выше…
С++ очень прост в том смысле, что классы С++ зачастую повторяют описание объектов реального мира (не в смысле прямого копирования, а в смысле названий, частично — функциональности и взаимоотношений, кроме того, в С++ бывают синтетические вспомогательные классы). Когда объект реального мира является или содержит другой объект реального мира (только один), в С++ применяется наследование классов.
Если объект-потомок может содержать нескольких однотипных — применяем включение объектов в класс потомка. Однотипное поведение классов выделяем в отдельный класс (который я буду называть классом подмешивания). Всё это отлично расписано у Алена Голуба [2], так что не буду повторять мастерски написанный текст. Скажу только что это простое мнемоническое правило следует запомнить и применять на автомате.
Гораздо менее канонический вопрос в распределении прав доступа к членам и функциям-членам класса. Все конечно знают про инкапсуляцию, все читали книжки. Что со всем этим делать на практике?
На практике происходит следующее. Я делаю класс Бункер, который описывает реальный бункер, к которому подключены датчики. Бункеры объединяются в цеха, всё это выводится в красивый интерфейс, как-то взаимодействует между собой и с человеком.
Спрашивается, сколько внутренней информации о Бункере я открыл другим классам? Отвечаю: вообще ничего. Понимаете? Все члены класса — приватные. Никто извне не должен знать что у класса внутри.
Причина ясна: поменял что-то в классе — и цепочка изменений потянулась по всему коду, который за секунду до этого был чист и отлажен как швейцарские часы.
Итак, Бункер не выдаёт наружу подробности своей реализации. При этом, он сам выводит себя в интерфейс, сам обрабатывает нажатия на свои контролы, сам выполняет всю работу, которая требует знания о том, как он устроен. Наружу открываются функции-члены, которые просят класс выполнить ту или иную работу: выведи себя в интерфейс, обработай нажатие мышки, сохрани себя в XML и т.д.
Вы наверное спросите: может не стоило так усложнять, почему бы не сделать классы более открытыми и дружелюбными? «Ни в коем случае» — отвечу я,- и вот почему.
Проходит два месяца работы программы, и заказчик просит меня сделать удалённое управление бункером. Потом удалённое управление требуется по разным каналам связи — от Интернет до сотовой сети и последовательного порта, причём каналы связи должны дублироваться. Время идёт, программа работает, и постепенно выясняется необходимость в удалённой синхронизации журналов действий операторов. А ещё через пару месяцев я выясняю что необходимо делать инкрементальную синхронизацию состояния бункеров и журнала при плохой связи (например, зашумлённый RS-485 или Wi-Fi на больших расстояниях).
Что я сделал? Я усложнил класс Бункер, который теперь работает либо локально, либо удалённо. Добавил ещё кое-какой код, который подхватывает настройки GSM/Ethernet/RS-232. И главное: всё остальное осталось как было. Все месяцы работы по тонкой настройке алгоритмов управления остались со мной!
То же самое относилось и к журналу, и ко всему остальному.
Если бы я вынес наружу хотя бы что-то из внутренней кухни Бункера, я даже боюсь представить сколько всего мне пришлось бы переписывать из 250 кБ управляющего кода.
Ещё раз повторю важный момент: структура программы на С++ отличается от С с точностью до наоборот. Там у нас были структуры и общие переменные. Здесь у нас объекты, которые суть чёрные ящики. Каждый объект — сам себе жнец, швец, на дуде игрец.
С другой стороны, сам код класса должен быть простым. Если требуется сложный функционал, я реализую его включением в класс внутренних объектов-помощников, либо использую классы подмешивания (но более осторожно, поскольку наследование является более сильной формой сцепления, чем включение).
Напоминаю, что get/set доступ — это чуть более завуалированный вариант открытого члена класса. С этим подходом нам не по пути.
Правильный способ взаимодействия с объектом — попросить его сделать то или иное действие.
Вместо того чтобы запрашивать данные, просим объект сделать нужное действие самостоятельно. Это необходимое условие стабильности больших проектов на С++. В некоторых случаях реализация какого-то действия — просто вернуть значение члена класса (то самое исключение, которое подтверждает правило).
И последнее. Любая структура имеет ограниченный потенциал роста. Возможно, заказчик затребует такие глобальные изменения, что потребуется менять интерфейс взаимодействия между классами. Более того, может потребоваться менять структуру классов. Но и это ещё не всё: есть объективные временные рамки на обкатку программы, в ходе которых становится очевидной необходимость создать новую структуру классов.
Хочу успокоить: всё это совершенно нормально. Программой пользуются — значит программа живёт, а значит меняется и развивается её структура. Прежде чем программа станет действительно хорошей, её, почти наверняка, придётся несколько раз переписать (мои самые серьёзные программы, как правило, переписываются дважды или трижды). Такая программа обладает отличным потенциалом роста, она больше не страдает «детскими болезнями» и, как правило, работает как те самые швейцарские часы. А все наработки из прошлой версии переносятся тем проще, чем более изолированными были изначальные классы.
- Бьёрн Страуструп. Язык программирования С++.
- Ален И. Голуб «Верёвка достаточной длины чтобы… выстрелить себе в ногу».
- структура программы
- инкапсуляция
Источник: habr.com