Как писать высококлассный код. Часть первая
Тема написания высококлассного кода всегда была востребованной и интересной. Так как последнее время требования к коду ужесточились, то мне приходится больше времени уделять внимания этому вопросу. Свои наблюдения, опыт постараюсь перенести в серии публикаций под общим названием «Как писать высококлассный код». Ни в коем случае не претендуя на полноту и абсолютность, надеюсь, что эти рекомендации и мысли будут кому-то полезны.
Идеальный код
Хотелось бы остановиться на вопросе, почему даже самый хороший код не может быть идеальным на 100%.
Итак, представьте такую ситуацию: вам необходимо написать функционал загрузки файла на сервер (пример взят из книги «Джоэл о программировании»). Казалось бы, простая задача, которую типичный программист решит за несколько минут. Надо всего лишь разместить элемент управления FileUpload и реализовать обработчик события, в котором указать, где на сервере сохранить файл. Из проверок необходимо только сделать поле обязательным или на сервере проверять размер файла (а лучше и то и другое).
Этот сценарий реализовывается с помощью такого кода:
<%@ Page Language="C#" %> <script runat="server"> protected void Button1_Click(object sender, EventArgs e) { if (FileUpload1.HasFile) try { FileUpload1.SaveAs("C:\\Uploads\\" + FileUpload1.FileName); Label1.Text = "File name: " + FileUpload1.PostedFile.FileName + "<br>" + FileUpload1.PostedFile.ContentLength + " kb<br>" + "Content type: " + FileUpload1.PostedFile.ContentType; } catch (Exception ex) { Label1.Text = "ERROR: " + ex.Message.ToString(); } else { Label1.Text = "You have not specified a file."; } } </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Upload Files</title> </head> <body> <form id="form1" runat="server"> <div> <asp:FileUpload ID="FileUpload1" runat="server" /><br /> <br /> <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Upload File" /> <br /> <br /> <asp:Label ID="Label1" runat="server"></asp:Label></div> </form> </body> </html>
Вот как бы и все, скажите вы и будете правы. Для 99% пользователей такой сценарий будет работать. Но всегда найдется ничтожно маленький процент пользователей, которые захотят загрузить вам на сервер видеофайл на 2ГБ – то ли по ошибке, то ли по приколу. Поэтому если ы хотите корректно обработать этот сценарий, вам, наверняка, понадобится прогрессбар. Причем желательно с статистикой потраченного и оставшегося времени. А это уже другой разговор и другие сроки. А еще нужно уточнить, что ваша программа будет делать, если на сервере вдруг закончиться место, или полетит файловая система, или кто-то поменяет права доступа на директорию или … ? Вариантов может быть много. В итоге, простейшая задача превращается в реализацию космического корабля.
Таким образом, вам необходимо решить, стоит ли 1% пользователей увеличения времени разработки в 10 раз? И очень часто оказывается, что идеальный код никому не нужен, так как он очень дорогой. Но полезный вывод из этой ситуации - всегда смотрите наперед, с какими сложностями могут столкнуться ваши пользователи.
Всегда нужно оценивать потенциальные проблемы перед написанием кода
Но иногда бывает и наоборот, когда простые решения не помогают. Недавно у меня была похожая ситуация, когда необходимо было импортировать данные из CSV формата в Microsoft SQL Server базу. Казалось бы, что может быть проще импортирования данных из популярного (и простейшего) формата в SQL Server? Пишем парсер CSV файла, прикручиваем Linq to SQL, мапим, делаем селекты по необходимым полям, получаем актуальные ID (так как надо было вставлять в несколько связанных по ключу таблиц) и вуаля! (Это один из возможных вариантов решения данной задачи, но не единственно правильный.)
Но простое и очевидное на первый взгляд решение не подошло по таким причинам:
- CSV файл может содержать некорректные строки
- размер CSV файла может составлять несколько гигабайт
- таблицы могут содержать несколько миллионов записей
- часть данных уже может быть в базе
- во время процесса импортирования на сервере может закончиться место (да, такое тоже может быть!)
В итоге весь предыдущий код пришлось выбросить и писать все «с нуля», чего не было бы, если я точно знал все требования к системе и подумал о возможных проблемах (что более важно).
Если бы эти вопросы были заданы вначале, то стиль написания кода стал бы более или менее понятен:
- CSV файл может содержать некорректные строки – ок, мы может пропускать такие сроки и отмечать их в каком-то файле с логами
- размер CSV файла может составлять несколько гигабайт – ок, тогда нам нужно использовать сжатие и чтение данных напрямую из архива
- таблицы могут содержать несколько миллионов записей – ок, в таком случае LINQ to SQL необходимо оптимизировать или вообще отказаться в пользу других боле быстрых и эффективных инструментов
- часть данных уже может быть в базе – ок, нужно настроить не только вставку, но и обновление данных
- во время процесса импортирования на сервере может закончиться место – ок, необходимо предусмотреть возможность отката транзакции
Но как говорится – негативный опыт тоже опыт. Но я немного отвлекся.
Какой код: одноразовый или продуктовый?
Что еще нужно сказать по поводу качества кода – всегда нужно решить, какой код вы будете писать – одноразовый, отладочный или продуктовый. От этого и зависит весь дальнейший процесс. Так получается, что я пишу очень много одноразового кода – в итоге ситуация, когда папок Work0 - WorkN может быть несколько (причем в каждом может быть до 50 различных проектов). Сначала я пытался как-то их систематизировать, но потом понял, что это бессмысленно. Да и не нужно это делать для одноразового кода – для того он и одноразовый, чтобы решить какую-то проблему или задачу один раз или проверить что-либо. Поэтому писать модульные тесты для такого кода не нужно. Понятно, что качественным должен быть тот код, который пойдет в итоге к заказчику. Остальное – при желании и наличии времени или денег, что , в принципе, одно и то же.
Еще хотел бы обратить внимание на ненормальные ситуации, когда качеству кода уделяют уж слишком много внимания.
Примеры таких ситуаций:
- требование 100% покрытия кода модульными тестами
- одинаковые требования к качеству кода для различного кода (одноразового и продуктового)
Эх, еще бы модульные тесты на модульные тесты писали бы :) Исключение, наверное, только программное обеспечение, критичное для жизни (life-critical systems).
Качественный код. Читабельность
Ну так как с типами кода мы определились, то можно уже и приступить непосредственно к тому, как же сделать код качественным. Качественный код должен быть простым и читабельным, не содержать ошибок, время на его изучение должно быть минимальным, его расширение и модификация – удобным.
Как сделать код читабельным:
- делать его самодокументированным, т.е. названия методов, классов должны говорить сами за себя, чтобы технический писатель, когда будет писать документацию, рвал на себе волосы, что за него сделали всю работу и его теперь, скорее всего, уволят.
- если вы перешли, например, из С++ разработки на C# - то в таком случае вам необходимо почитать документацию, чтобы специфически названных переменных аля m_var или i_number не было.
- Code conventions – как без них? Это свод правил, или даже сказать, рекомендаций по наименованию классов, методов и т.д. Такой документ, как правило, есть для всех языков программирования и технологий, а для популярных языков – даже не один. В таком случае все члены команды должны договориться о том, какой вариант рекомендаций использовать.
- использовать best practices, которые можно подчерпнуть из книг, специфических блогов (например, блога Эрика Липперта) и от команды Patterns & Practices, например Unity
Качественный код. Уровни абстракций
Глупо спорить с тем, что код должен быть как можно более независим. Для этого люди придумали объектно-ориентированное программирование со всеми инкапсуляциями, наследованиями, виртуальными методами и сопутствующими технологиями и подходами. Что в этом плохого? Ничего особенного, если не брать во внимание некоторых архитекторов, которые иногда слишком близко принимают поставленную задачу к сердцу, в результате выходит ситуация, когда приложение для складывания два числа может содержать три уровня наследования и около десятка классов. Таких людей еще называют космическими астронавтами. Ситуация, конечно же, надуманная, но суть ясна – с уровнями абстракций можно перестараться. В общем, посыл простой: использовать сложные архитектурные решения только там, где необходимо. Если для решения задачи можно обойтись одной функцией без наследования и инкапсуляции, то качественный код будет именно таким. Т.е. качественной код должен быть настолько простой насколько это можно.
Процитирую Джоэла:
Когда вы поднимаетесь слишком высоко, наполненный абстракциями, вы задыхаетесь из-за отсутствия кислорода. Иногда мыслители просто не знают, когда остановиться, и они создают абсурдные, всеобъемлющие, высокоуровневые картины устройства вселенной, которые являются хорошими и изящными, но фактически не означают вообще ничего. Не дайте Астронавтам Архитектуры вас запугать.
В первой части я рассмотрел общие вопросы, связанные с написанием высококачественного кода. Для кого-то этот материал будет мало полезен из-за того, что в нем рассказаны очевидные вещи, но хотелось бы преподносить информацию в более или менее структурированном виде. В следующей статье поговорим детально об инструментах тестирования качества кода.
Компании из статьи
Microsoft Украина | Украинское подразделение компании Microsoft. |