Как писать высококлассный код. Часть первая

суббота, 29 января 2011, Александр Краковецкий

Тема написания высококлассного кода всегда была востребованной и интересной. Так как последнее время требования к коду ужесточились, то мне приходится больше времени уделять внимания этому вопросу. Свои наблюдения, опыт постараюсь перенести в серии публикаций под общим названием «Как писать высококлассный код». Ни в коем случае не претендуя на полноту и абсолютность, надеюсь, что эти рекомендации и мысли будут кому-то полезны.

Идеальный код

Хотелось бы остановиться на вопросе, почему даже самый хороший код не может быть идеальным на 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" />&nbsp;<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 Украина


Сайт:
http://www.microsoft.com/ukr/ua/

Microsoft Украина Украинское подразделение компании Microsoft.

Ищите нас в интернетах!

Комментарии

Свежие вакансии