Не совершает ошибок только тот, кто ничего не делает. Программисты - тоже люди и постоянно совершают ошибки. Рассмотрим пять типичных ошибок проектирования и программирования, встречающихся в исходном коде программ, но не влияющих на их работоспособность. Чаще всего они появляются из-за невнимательности, лени и непредусмотрительности разработчиков.
Не всегда получается предусмотреть направление развития программы и/или библиотеки. Это может привести к тому, что имя класса или интерфейса оказывается слишком узким для его фактической области применения.
Первый вариант подобной проблемы состоит в том, что имя перестает соответствовать иерархии наследования. Например, изначально в корне иерархии находился интерфейс Camera
. Затем обнаружилось, что крайне удобно реализовать класс для работы с электронным телескопом Telescope
, унаследовав этот интерфейс, чтобы иметь возможность использовать существующий полиморфный код. Но телескоп - это не камера, поэтому не стоит нарушать логику.
Другая форма этой ошибки связана с расширением функциональных возможностей класса. На ум сразу приходит класс XMLHttpRequest
, включенный в стандартную библиотеки языка Javascript. На самом деле он может работать не только с данными в формате XML
, но это не очевидно из его названия, что может вводить в заблуждение.
Справиться с такой ошибкой можно средствами рефакторинга или операции поиска/замены по исходному коду. Но есть одно но. Если вы разрабатываете не самодостаточный проект, а прикладную библиотеку, то сделать это может быть очень сложно, ведь от вашего кода зависят другие проекты. Поэтому у вас не получится просто так изменить имя (как в ситуации с XMLHttpRequest
). В этом случае класс можно переименовать, но для совместимости создать псевдоним, соответствующий его старому названию. При этом старое название нужно пометить как нерекомендуемое к использованию (deprecated
).
См: Пример полиморфизма в C++ на основе ООП
Наследование - это полезный и мощный прием программирования. Но нужно знать меру. Если в приложении есть некая сущность, то это не означает, что для нее должен обязательно существовать класс.
Например, если вы разрабатываете более или менее сложную игру, то в ней будет множество объектов: игровой персонаж, враги, препятствия, оружие и т.д. Если для каждой отдельной сущности (например, меч, пистолет, винтовка и т.д.) создать класс, то программа просто утонет в сотнях, а то и тысячах похожих классов.
Решение заключается в том, чтобы выделить несколько базовых классов, характеризующих понятия: враг, препятствие, оружие и т.д. Далее самих врагов можно разделить на наземных, летающих и т.д., задать им некие типичные характеристики (скорость движения, размер, сила атаки и т.д.) в виде полей класса. То же самое проделать по необходимости для других сущностей. Для динамических аспектов поведения (траектория перемещения и элементы искусственного интеллекта) используйте делегирование, которое также полезно применять для параметризации поведения классов.
См: Профессия программиста: Абстрактное мышление
Иногда, когда поджимают сроки сдачи проекта, возникает соблазн что-то где-то срезать и упростить. Почти всегда это приводит к проблемам в будущем.
Например, имеется функция-обработчик нажатия кнопки. Она выполняет некую последовательность действий. Вдруг мы замечаем, что эту же последовательность действий нужно воспроизвести при возникновении того или иного условия в работе программы. Сложно устоять, и не сделать вызов обработчика напрямую. Однако это приводит к появлению неочевидных зависимостей, которые в будущем превратятся в ошибки, если нам понадобится изменить код обработчика.
Правильное решение в данной ситуации - создание еще одной функции (а может и нескольких), которая имеет осмысленное имя и делает то, что изначально происходило в обработчике. Тогда эта новая функция будет вызываться и в обработчике, и во всех местах, где это необходимо. Когда придет время что-то менять, то если изменения имеют общий характер, их можно внести в созданную функцию, иначе - индивидуально по месту, не затрагивая логику работы остального кода.
Дублирование в программировании обычно приносит только проблемы. Но одно дело, когда оно происходит случайно, а другое, когда код дублируется намерено. С этим сталкивается большинство разработчиков. Иногда кажется, что проще скопировать кусок кода из одного места и вставить его в другое, а не выделять отдельную функцию (или класс), для которой еще нужно найти подходящее место, придумать хорошее имя и удачную сигнатуру. Но это ощущение обманчиво.
Очень часто оказывается, что если некий фрагмент пришлось копировать один раз, то его придется копировать и еще. Хуже всего, если разработчик уже сам понимает всю глубину проблемы, но не может отступиться от начатого, и в очередной раз пользуется "копи-пастом". Таким образом он топит себя. Поскольку, если в будущем выяснится, что скопированный код содержал ошибку и ее нужно исправить в каждом продублированном фрагменте, то ему предстоят "веселые" часы рутинной работы с массой потенциальных ошибок.
Единственное правильное решение в этой ситуации - не лениться и создавать вспомогательные функции и классы вовремя. В будущем потраченное время окупится многократно и вы сможете избежать множества сложностей и проблем. Если вовремя это сделать не удалось, то могут помочь инструменты рефакторинга, которые во многих IDE стали настолько интеллектуальными, что сами умеют находить дублирование при создании функций по фрагментам кода.
Этот тип ошибок во многом пересекается с ошибками № 1 и 3, но все же я решил выделить его отдельно, поскольку между ними имеются концептуальные отличия.
Часто из-за спешки мы даем функциям и переменным не самые подходящие имена. Иногда это связано с неполным пониманием предметной области или с ошибками проектирования. Но это не оправдание.
Конечно, придерживаться 100%-ой чистоты тоже нет необходимости. В простом for
-цикле вполне можно использовать переменную i
, но чем больше область видимости, тем осмысленнее должно быть имя.
Еще хуже, если имя осмысленное, но вводит в заблуждение. Т.е. из названия следует одно, а на самом деле переменная хранит что-то другое; или функция делает не то, что от нее можно ожидать (например, имеются побочные эффекты).
Решение заключается в критическом анализе кода, ведь чаще всего простым переименованием не обойтись. Плохое имя переменной или функции не редко связано с ошибками проектирования - что-то находится не на своем месте или делает больше, чем должно. Поэтому устранение проблемы требует аккуратности и временных затрат. Но оно того стоит, ведь чем яснее код, тем меньше шансов, что в нем появятся ошибки.
См: Принцип единой ответственности
Anonymous:
Здравствуйте, не могли бы вы помочь мне разобраться с устройством работы программы которая будет удалять файлы из реестра.
Здравствуйте. У Вас уже имеются наработки, в которых требуется разобраться? Или Вас интересует то, как создать некий упрощенный аналог regedit?
Наработки отсутствуют, по скольку я буквально вчера начал разбираться с єтим вопросом. По сути я хочу создать программу которая будет входить в регистр, находить запрашиваемый ключ и удалять его.
А просто удалить из командной строки нельзя? Без создания программ.
Anonymous:
А просто удалить из командной строки нельзя? Без создания программ.
http://www.windowsfaq.ru/content/view/301/60/
Удалить, конечно, можно. Если задача заключается именно в этом. Но я так понял, что имеется чисто прикладной интерес относительно вопроса реализации подобной функциональности в форме приложения.
Если вопрос еще актуален, то могу подготовить пример программы к следующей статье.
Да актуален будет интересная реализация =)
Anonymous:
Да актуален будет интересная реализация =)
Ок. Займусь в ближайшее время =)
Не подскажите ли, когда выйдет следующая статья?
Anonymous:
Не подскажите ли, когда выйдет следующая статья?
Здравствуйте. Я немного приболел, поэтому выход статей задержался. Рассчитываю, что в начале следующей неделе смогу опубликовать.
Anonymous
Здравствуйте, не могли бы вы помочь мне разобраться с устройством работы программы которая будет удалять файлы из реестра.