О чем речь?
Речь о поддержке и развитии существующей кодовой базы, о достижении успеха. Успех шаг за шагом, успех каждого изменения, успех продукта – вот наша цель.
Принципы успешного проекта
Успеха можно достичь только тогда, когда все изменения идут “в плюс”. Вы интуитивно почувствуете это. Когда вместо того, чтобы становиться все более ненадежной и запутанной, конструкция становиться все более легкой и мощной; это непросто, но достижимо. Однажды я достиг такой ситуации с одним проектом, и теперь хочу рассказать об этом. Я придерживался определенных принципов при написании кода, и, похоже, они сработали. Вот эти принципы:
- Используйте языки со статической типизацией, чтобы зафиксировать гарантии;
- Избегайте “дикой” многопоточности – используйте идеомы взаимодействия; Идеал – система, которую можно всю пройти отладчиком
- Избегайте “диких” сетевых соединений – используйте идеомы и готовые протоколы
- Проверяйте тестами все куски кода. Пишите юнит тесты и не бросайте их.
- Периодически посещайте давно не вспоминаемые куски кода и их тесты. Наиболее давно не просматриваемый код как правило содержит ошибки
- Непрерывно улучшайте код – делайте рефакторинг, ремоделинг и пересмотр структуры каталогов
- Стремитесь к модульности
- Напишите свой мини-конструктор
- Используйте те средства, которые Вы хорошо знаете и многократно проверили
Статическая типизация
Компилятор – Ваш друг. Статическая типизация – рулез. Статическая дает гарантии. Гарантии чего? Быстрого обнаружения ошибки, причем в больишнстве случаев – на стадии компиляции. Рулезность этого можно прочувствовать только тогда, когда попрограммируешь на Лиспе, Эрланге и пр. языках с динамической типизацией. Малейшая ошибка – и привет, ищи, где ты там поле не того типа передал…Кто как, а я очень неуверенно себя чувствую. Для больших проектов сакс, имхо. Для больших проектов нужны твердые гарантии. Гарантии – вот что нам нужно.
“Дикая” многопоточность
Начинающие программисты, ощущая собственную крутизну, наворачивают в коде десятки потоков. Разумеется, такой код в свою очередь, очень часто “наворачивается” с корами. Запрограммировать потоки правильно можно двумя способами:
- построить приложение, в котором потоки на 100% не взаимодействуют друг с другом (например-принадлежат разным подпроцессам) . Сделать поток атрибутом кода;
- придерживаться идеом взаимодействия.
Если с первым случаем все ясно, то второй требует пояснения. Что есть идеома? Это шаблон, правило, метода. Например “процессы и сообщения”. Или “потоко-безопасные переменные”. Или “пул потоков”. Этот путь затратный. Вы должны сконструировать имплементацию идеомы, доказать ее правильность и корректность реализации, и уж после того использовать. В моем случае была идеома “Потоко-безопасные переменные”. Пришлось очень и очень постараться, пока я все сделал как надо. (Примечание : Я видел десятки программеров, которые мучаются с многопоточными приложениями (ой, надо подебажиться, что-то там где-то не туда залезло). Не стоит терять время – используйте идеомы.)
“Дикие” сетевые соединения
Я и все мы нисколько не сомневаемся в Вашей программерской крутизне. Честное слово. Но дело не в этом. Дело в подводных камнях, которые в изобилии торчат по ходу проекта. Сетевое взаимодействие – штука сложная. Попробуйте избежать ручного программирования сокетов. Если все же не получается, потратьте время на изучение поведения TCP/IP, сокетов, коннектов-дисконнектов, поведении при обрывах и пр.
Не следует нарушать принятые соглашения при организации сетевых протоколов. Раз целые числа передаются в сетевом порядке – передавайте в сетевом порядке. Опять таки, я знаю, что Вы – крутой программист. Фишка в том, что потом, когда Вашу софтину придется сопрягать с каким нибудь компом RISC и модулем на языке Erlang, то Вы обнаружите, что сетевой порядок байт поддержан на уровне языка, а для обычного придется писать модуль сопряжения…
Используйте стандартные протоколы, даже если Вы пишете по сокетам сами. Если протокол простой – используйте текстовые строки с разделителем. Если данных много или критично быстродействие – используйте бинарный протокол с заголовком (опять-таки тоже соображение, что и в предыдущем абзаце). Используйте HTTP – будете приятно удивлены, когда надо будет передавать Ваши данные через proxy.
Проверяйте u-тестами все куски кода
Пишите юнит-тесты. Пишите ИЗ юнит тестов, пишите тесты ДО кода. Идеальный код = мультипоточные идеомы + код, на 100% охваченный тестами. Некоторые языки, например Erlang, вообще навязывают такого рода модель. Все модули, классы, подпрограммы, которые можно пройти в отладчике, и которые не зависят от взаимодействия потоков – должны быть охвачены тестами. Следите за тестами, не дайте им устаревать.
Периодически просматривайте старый код
Чем дольше Вы не смотрите на код, тем выше шансы, что там ошибка. Просмативайте старый код, рефакторите его, запускайте тесты. На все ошибочные ситуации пишите тесты, чтобы ошибка не имела шансов всплыть снова и остаться необнаруженной. Пишите дополнительные тесты на старый код. Если Вы придумали более удачный тест – напишите его.
Непрерывно улучшайте код
Думайте над кодом. Думайте над кодом. Думайте над кодом. Улучшайте. Созидайте новые абстракции и разрушайте отжившие. Таков закон жизни. Закон природы. Меняйте реализации на более эффективные. Проводите испытания – на утечки памяти, быстродействие, безопасность.
Стремитесь к модульности
Делайте систему ортогональной. Система шифрования трафика не должна зависеть от сокетов. Протокол верхнего уровня не должен быть привязан к сети. Отдельные сетевые функции не должны зависеть друг от друга. Старайтесь писать серверы без состояния настолько, насколько это возможно.
Напишите свой мини-конструктор
Напишите для себя конструктор – особенно, если Вы используете C++. Продумайте и напишите. В Вашем распоряжении шаблоны, макросы, перегрузка операторов. Тщательно подумайте над тем мини-ЯЗЫКом, на котором Вы будете строить дальнейшую систему. Это сильно облегчит Вам работу. Просмотрите примеры такого “языка в языке” – система COM для C++ в Windows, плугины для FireFox, etc.
Используйте те средства, которые хорошо знаете
Что значит “знать средство”? Это значит, знать его выверты и тонкости. CORBA нестабильна при плохой связи, сокеты не выполняют опроса соединения, malloc в сто раз хуже VirtualAlloc, если надо выделить 50Мгб… Это не зависит от языка. Можно написать отличную прогу на плоском C и отвратительную – на C#. (Я помню, как был поражен, когда увидел сорцы Quake).
Моя история
(Вот, уже мемуары пишу, совсем старый стал). Мы писали один сложны сетевой сервис на Объектном Паскале (Дельфи). Это неправда, что это невозможно – вы просто не умеете. Также неправда, что Паскаль плохой язык. Мы достигли успеха. Одна инсталляция сервера проработала без выключения год (!). К моменту старта проекта я уже отлично разбирался в поведении сокетов и на быстрых линиях, и на модемах, и через proxy и пр. Также, я был осведомлен о недоработках в DCOM. Кроме того, я действительно разбирался в мутексах, семафорах и прочей виндозной начинке. Посему я принял решение писать на минимальном уровне – на уровне сокетов. И ни разу не пожалел об этом. Важно знать, как правильно готовить…
Перед моим мысленным взором стояли сорцы Quake. Я восхищался авторами. Плоский C – машина анимации – язык моделей – игра. Я портировал свою библиотеку сокетов с C++ на Паскаль (кое какие фенечки, радующие глаз, потерялись при переносе, конечно). Я создал свой конструктор сетевого взаимодействия. Протокол был построен таким образом, что он ничего не знал о сети – это была такая идеома. Стратагема “прозрачность сети, RPC”, вещь старая и известная. Подменяя RPC LPC, я мог тестировать код клиента и сервера насквозь, и писать для него тесты. Это офигенно повысило надежность – практически, я получил гарантию.
Я непрерывно совершенстовал код. Вот тогда, именно в тот момент, я понял, что значит “работать в плюс”. Я использовал все те инструменты, которые мы обычно используем, когда пишем на C++ – BoundsChecker, AQTime, некоторые другие компоненты. Для взаимодействия потоков я использовал идеому “rendez-vous”, заимствованную из языка Ada. Определенное время, конечно, пришлось потратить на реализацию и доказательство корректности. Но оно того стоило. Результат превзошел все ожидания- я совершенно избавился от ошибок, наведенных мультипоточностью.
Структуру каталогов за время проекта пришлось основательно перетряхнуть раз пять, не меньше. Это правильно. Написание правильной программы – мучительный, трудоемкий, неряшливый местами процесс. Все наши представления о проекте (особенно-первоначальные представления) устаревают и становятся неадекватными.
Классы рефакторились ежедневно. Я четко следовал шаблону “принадлежность метода”.
Продолжение следует…