Организация проекта - та небольшая, но весьма важная область, которую обычно обходят своим вниманием во всех книгах про код: потому что писать сложно, а уж как он будет разделён и структурирован - дело десятое.
И хотя существуют целые руководства для описания кода, обычно они ориентированы не столько на отдельного разработчика, сколько являются правилами для отдельной команды.
Так уж вышло, что у меня тоже накопился небольшой багаж способов организации файлов и модулей в коде, которые, на мой взгляд, имеют определённые преимущества. Ими и хочу поделиться.
Двойные ссылки на модули
Довольно типичная история, когда внутри какого-то модуля есть функционал, которым необходимо поделиться с другими модулями. И далеко не всегда он, что называется, на поверхности, а может разместиться глубоко внутри.
Предположим, есть модуль "криптография", внутри него модуль "контрольные суммы", а внутри него функция "посчитать единичные биты", которая используется для внутренних целей.
И есть модуль "Оценка", которому для своих нужд требуется та же самая функция подсчёта единичных бит, причём несколько раз и на разной "глубине" модуля.
Путей решения этой задачи несколько: объявить две одинаковые функции, прокинуть путь доступа до требуемой функции внутри модуля криптографии, добавить "общий" модуль, внутри которого будет функция.
Но чтобы соблюсти принцип соответствия (каждый функционал расположен там, где он используется в первую очередь), принцип закрытости модулей ("наружу" смотрит только то, что необходимо) и принцип кратчайшего пути (любой требуемый метод находится на предельно близком расстоянии от точки вызова) - можно пойти по иному пути: в упомянутом выше "общем" модуле сделать ссылку на требуемый метод (повторная публикация).
Это позволит, во-первых, сохранить место происхождения функции; во-вторых, позволит описать "длинный" маршрут доступа (через вложения во вложения модулей) только один раз; в-третьих, позволит обращаться к какой-то функции как к общей, при том, что она будет объявлена лишь единожды и всё равно будет относиться только к целевому модулю (в нашем случае - "криптография").
Есть, правда, и отрицательная сторона вопроса: необходимо администрировать доступ к функционалу и, если подобных функций станет слишком много - надо будет думать, как это упростить.
Плоская структура вместо древовидной
Даже небольшой проект имеет, зачастую, много файлов. Их, чаще всего, компонуют по принципу дерева: внутри компонента файлы его составляющие. Есть частные случаи, когда файлы называются определённым образом, например: компонент.функционал.расширение и все они находятся в одной папке.
Как по мне, работа с большим массивом файлов очень неудобна, но есть подход, который позволяет это делать относительно эффективно. Однако, для него потребуется IDE с очень хорошо выполняющей функцию навигации по коду. Но нужно оговориться: в коде не редкость одинаковых названий разных модулей, поэтому требуется не просто IDE, которая позволяет перейти к файлу, в котором объявлен функционал, а перейти именно к тому файлу, где этот функционал объявлен.
В этом случае можно хранить все файлы без разделения на подпапки, но правила именования внутри своего проекта лучше поддерживать. А перемещение по проекту уже будет осуществляться посредством встроенного функционала IDE.
Обратной стороной данного подхода является гарантированная потеря устойчивости проекта: отказ от IDE сразу приводит к тому, что разобраться в месиве файлов становится невозможным.
Импорт файла и метода
Чаще всего, имя файла и его содержимое отчасти или полностью совпадают. Это позволяет относительно легко понять, что находится внутри. Проблемы возникают в том случае, если надо делать рефакторинг, переработку кода по-нашему.
Файл, нередко, импортируется в разные уголки проекта, особенно, если он сам по себе востребован. Но по мере роста поддерживаемых функций может потребоваться его переименовать. И тут начинаются проблемы: найти все места, где объявлен импорт и изменить не только наименование файла импорта, но и имя класса или метода (методов).
Можно пойти пойти по другому пути: изначально любой экспорт "пропускать" через некоторый файл-прослойку, где требуемый функционал импортируется и сразу экспортируется с теми же именами.
В чём суть: замена имени файла или имени класса позволит произвести изменение лишь в одном месте (либо сделать два экспорта: со старым и новым именами), а потом, постепенно (при необходимости) заменить все остальные места, где функционал используется без единомоментной поломки всего проекта.
Недостатки такого подхода очевидны: лишний файл-прослойка и неверные имена в случае изменения. Достоинства - также не очевидны: возможность провести изменения по переименовыванию сразу, а методы в проекте переименовать позже (такое требуется не часто, но когда требуется - оказывается очень полезным).
Выведение метода из класса
Случается, что класс сильно разрастается, становясь таким прямо ну очень пухлым. И, нередко, это связано с тем, что есть один или несколько методов, которые делают очень много и их, хорошо бы, разделить на несколько, но такой подход превращает класс в свалку приватных методов, которые будут использоваться в интересах всего одного.
Но есть случаи, когда зависимости метода от класса таковы, что можно метод вынести в отдельный файл, передавая ему лишь необходимые параметры (иногда для этого достаточно только ссылки на объект от класса порождённого, иногда - ещё набор приватных свойств). При этом часть обработки (скажем, переназначение приватных свойств или вызов приватных методов) может остаться внутри изначального метода, а всё остальное - вынести.
Преимущества такого подхода в том, что он позволяет избежать свалки внутри класса (или модуля), разделив изначально большой метод на несколько методов попроще, оставив внутри самого класса только конечные результаты обработки (если это вообще потребуется).
Минусы также присутствуют: вынесение логичного функционала из класса во внешние методы или классы, всё же, провоцируют некоторый хаос; просто он находится где-то вовне и на простоту чтения класса не влияет, хотя сложность изначальной конструкции снижает не то, чтобы сильно.
Отказ от обработки ошибок
Ошибки возникают всегда: как в коде, так и в логике работы. Обычно, их стараются обрабатывать таким образом, чтобы пользователь смог продолжать работу с приложением несмотря ни на что. Чаще всего используются структуры типа try-catch, но есть и иные.
Самая большая трудность обработки ошибок заключается в том, что предусмотреть ошибку и обработать её последствия, зачастую, намного трудозатратней, чем реализовать функционал. Притом, что такая ошибка может никогда и не появиться.
Если мы говорим о критической инфраструктуре - такой подход не дозволяется, но если про прототипирование (или приложение не то, чтобы обрабатывает сильно чувствительные данные, а мы не то, чтобы сильно переживаем за положительный настрой пользователя), то обработкой ошибок, на начальном этапе, можно пренебречь.
Но тут важно учитывать несколько факторов.
Во-первых, определяется уровень "падения" приложения. То есть изначально мы для себя решаем, какой объём данных может быть потерян в результате ошибки: это может быть сброс всего приложения, модуля или отдельной функциональности: в журнал пишется ошибка и ставится дополнительный критерий.
Во-вторых, требуется определённая структура составления кода и метод журналирования ошибок и стандартизированный функционал сброса данных (закрытие формы, модуля или приложения целиком).
В-третьих, можно добавить (но это для особых случаев) механизм восстановления данных после сбоя: сброшенные данные восстанавливаются после повторного открытия приложения/модуля/функциональности. Частично или полностью.
В чём прелесть такого подхода? Относительно быстрая разработка, довольно скорый сбор потенциально опасных участков и локализация их по факту. Также экономия ресурса на написание обработчиков для случаев, которые никогда не произойдут.
Из недостатков - крайне ненадёжное приложение, которое сойдёт для прототипа, но потом потребует закрывать объёмный технический долг. Впрочем, нестабильно работающее приложение вам простят, а вот пусть и хорошо, но так и не реализованное - нет.
Заключение
Предлагаемые мной подходы тут - неоднозначны. Они идут несколько вразрез с тем, чему учат и к чему привычно большинство разработчиков. А предлагаемые задачи можно решить и красивей, и изящней. Но, недаром говорят, что перфекционизм - это форма прокрастинации. Поэтому такие подходы также имеют право на существование, просто для своих целей.