Меню
Код
Код
#статьи
  • 17290

Ужасы чужого кода: как найти смысл и не умереть

Даже самым крутым программистам трудно читать чужой код. Узнайте, почему это так страшно и как с этим справиться.

В жизни каждого разработчика наступает момент, когда нужно взяться за код, написанный другим человеком. Это может быть связано с поддержкой старого проекта, оптимизацией legacy-кода, переделыванием приложения, которое не доделал другой программист.

Хорошо, если этот код написан по архитектурному паттерну, у него есть документация, комментарии или хотя бы понятные названия переменных. Однако чаще всего приходится работать с говнокодом (это термин, если что).

В этой статье я опишу личный опыт чтения ужасного кода. Поэтому приготовьтесь сначала лицезреть моё страдание и только потом получить какие-то полезные советы.

Евгений Кучерявый

Пишет о программировании, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.


Виновник торжества

Мне и раньше приходилось сталкиваться с плохим кодом (например, своим), но недавно попался настолько ужасный, что я решил написать об этом статью.

Для начала попробуйте понять, что делает этот цикл, зная только, что приложение работает с 3D-графикой:

for (var k=0;k<gm.Count;k++) {
    object.par.mn_mx[0]=Math.Min(object.par.mn_mx[0],gm.getX(k));
    object.par.mn_mx[3]=Math.Max(object.par.mn_mx[3],gm.getX(k));
    object.par.mn_mx[1]=Math.Min(object.par.mn_mx[1],gm.getY(k));
    object.par.mn_mx[4]=Math.Max(object.par.mn_mx[4],gm.getY(k));
    object.par.mn_mx[2]=Math.Min(object.par.mn_mx[2],gm.getZ(k));
    object.par.mn_mx[5]=Math.Max(object.par.mn_mx[5],gm.getZ(k));
    msh.par.mn_mx[0]=Math.Min(msh.par.mn_mx[0],gm.getX(k));
    msh.par.mn_mx[3]=Math.Max(msh.par.mn_mx[3],gm.getX(k));
    msh.par.mn_mx[1]=Math.Min(msh.par.mn_mx[1],gm.getY(k));
    msh.par.mn_mx[4]=Math.Max(msh.par.mn_mx[4],gm.getY(k));
    msh.par.mn_mx[2]=Math.Min(msh.par.mn_mx[2],gm.getZ(k));
    msh.par.mn_mx[5]=Math.Max(msh.par.mn_mx[5],gm.getZ(k));
};

Первое, что бросается в глаза, — нарушение традиций в выборе имени счётчика.

Чтобы разобраться, что тут происходит, нужно понять, что такое msh, object и gm. С последним всё предельно просто:

var gm=oo.children[i].geometry.attributes.position;

Теперь остаётся только понять, что такое oo. Это очень хороший вопрос, потому что oo — это глобальная переменная, объявление которой нужно ещё поискать.

Вот другой фрагмент:

public static float len_po_poin(float[] t1,float[] t2){
    return Math.Sqrt(Math.Pow(t2[0]-t1[0],2)+Math.Pow(t2[1]-t1[1],2)+Math.Pow(t2[2]-t1[2],2));
};

Название метода не вносит никакой ясности. Не говоря уже о том, что разработчик игнорирует соглашение об идентификаторах. Он даже единого стиля не придерживается, потому что в этом же файле можно встретить метод, который называется getPointOfIntersection ().

Складывается ощущение, что разработчик не знает главного правила программирования:


«Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте».

Джон Ф. Вудс


Как с этим жить

Очевидно, что невозможно заставить всех писать чистый и понятный код. Поэтому единственный выход — научиться читать всё, что попадается под руки. Это умение можно разделить на два навыка: чтение и расшифровка (эти названия условные).

Расшифровка может пригодиться, когда нужно срочно понять, как работает код. Особенно если он настолько ужасный, что просто пытаться вникнуть в проект — глупо. Рассмотрим на примере:

public static bool o_col_ch(int x1,int y1,int w1,int h1,int x2,int y2,int w2,int h2){if(x1+w1>x2&&x1<x2+w2&&y1+h1>y2&&y1<y2+h2) return true; return false;}

Это достаточно небольшой метод, который, однако, записан таким образом, что невозможно понять, что он делает. В этом случае не стоит тратить силы, чтобы вникнуть, — лучше сразу привести его в человеческий вид.

Для начала можно расставить пробелы и переносы строк:

public static bool o_col_ch(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2)
{
    if(x1 + w1 > x2 
    && x1 < x2 + w2
    && y1 + h1 > y2 
    && y1 < y2 + h2) 
    {
        return true; 
    }
    
    return false;
}

Сделать это можно с помощью специальных инструментов, но лучше попробовать вручную. Возможно, в процессе получится что-то понять. Попутно можно добавить комментарии:

public static bool o_col_ch(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2) //Метод принимает координаты и ширину и высоту двух объектов
{
    if(x1 + w1 > x2 //Если X + ширина первого объекта больше, чем X второго объекта
    && x1 < x2 + w2 //Если X первого объекта меньше, чем X + ширина второго объекта
    && y1 + h1 > y2 //Если Y + высота первого объекта больше, чем Y второго объекта
    && y1 < y2 + h2) //Если Y первого объекта меньше, чем Y + высота второго объекта
    {
        return true; //Возвращается true
    }
    
    return false; //Иначе возвращается false
}

Становится понятно, что метод проверяет коллизию двух двумерных объектов. Поэтому можно поменять названия переменных и самого метода:

public static bool CheckCollision(int x1, int y1, int widht1, int height1, int x2, int y2, int width2, int height2) //Метод проверяет коллизию двух объектов по их координатам, ширине и высоте
{
    if(x1 + width1 > x2 //Если X + ширина первого объекта больше, чем X второго объекта
    && x1 < x2 + width2 //Если X первого объекта меньше, чем X + ширина второго объекта
    && y1 + height1 > y2 //Если Y + высота первого объекта больше, чем Y второго объекта
    && y1 < y2 + height2) //Если Y первого объекта меньше, чем Y + высота второго объекта
    {
        return true; //Возвращается true
    }
    
    return false; //Иначе возвращается false
}

Когда весь код приобретёт более-менее человеческий вид, можно будет провести рефакторинг:

  • разобраться с зависимостями;
  • использовать полиморфизм;
  • написать документацию;
  • удалить повторяющийся код и так далее.

Например, можно вместо координат принимать объекты:

public static bool CheckCollision(GameObject a, GameObject b)
{
    if(a.X + a.Width > b.X //Если X + ширина первого объекта больше, чем X второго объекта
    && a.X < b.X + b.Width //Если X первого объекта меньше, чем X + ширина второго объекта
    && a.Y + a.Height > b.Y //Если Y + высота первого объекта больше, чем Y второго объекта
    && a.Y < b.Y + b.Height) //Если Y первого объекта меньше, чем Y + высота второго объекта
    {
        return true;
    }
    
    return false;
}

Всё это должно помочь разобраться в коде здесь и сейчас. Если же нужно постоянно работать с чужими проектами, то есть смысл попрактиковаться в чтении. Есть несколько простых советов, которые помогут в этом нелёгком деле.

1. Пробуйте писать в разных стилях

Обычно я пишу вот так:

if (true)
{
    //...
}
else
{
    //...
}

Даже если нужно выполнить одну инструкцию, я всё равно использую фигурные скобки. Мне кажется, так легче понять, что является частью ветвления, а что — нет.

Но иногда стоит посмотреть, как пишут другие, и попробовать так же. Например, вот так:

if (true) 
    //...
else 
    //...

Или так:

if (true) //...
else //...

Или даже так:

if (true) /*...*/ else //...

Если часто практиковаться, можно привыкнуть, и тогда читать такие конструкции будет проще.

2. Читайте чужой код

Попробуйте зайти в случайный репозиторий на GitHub и разобраться, как там всё устроено и как оно работает. Изучите несколько проектов, чтобы научиться понимать разные стили.

Подумайте, почему разработчик пишет так, а не иначе, как он составляет документацию и так далее. Так вы не только научитесь читать чужой код, но и, возможно, найдёте пару интересных приёмов, которые захотите использовать сами.

3. Давайте другим читать ваш код

Дайте другому разработчику посмотреть ваш проект. Сразу будьте готовы к тому, что ваш код тоже так себе. Обратная связь поможет понять, что вы делаете не так, а если повезёт, получите несколько рекомендаций, как улучшить свой код.

В процессе вы можете понаблюдать, что делают более опытные коллеги, чтобы вникнуть в ваш проект.

4. Попробуйте прочесть код, который писали давно

Скорее всего, сначала вы ничего не поймёте, а потом почувствуете сильный стыд. Это нормально и случается со всеми программистами.

Сейчас вы уже лучше, чем были когда-то давно, поэтому сможете увидеть, что делали не так. Возможно, вы заметите, что до сих пор используете те же самые приёмы, и исправите это.

5. Больше рефакторинга

Иногда код необходимо переделать, даже если он работает. Конечно, это касается в первую очередь проектов, которые нужно поддерживать. Если не прибегать к рефакторингу, то придётся постоянно держать в голове весь плохой код, чтобы не приходилось каждый раз вникать заново. Кроме того, вы вряд ли заметите баги и уязвимости, потому что весь код выглядит как одна большая уязвимость.

Заключение

Последний совет: не обманывайте себя. Вы, я и любые другие разработчики пишут ужасно. Просто потому, что так устроено программирование — приложения не могут быть безупречными и совсем понятными.

Однако это не значит, что можно окончательно забить на чистоту и читаемость кода. Просто нужно понимать, что мастерство в программировании, как и в любом другом деле, это не пункт назначения — это сам путь.

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

Курс

Профессия C#-разработчик


130 часов — и вы научитесь писать программы на языке, созданном Microsoft. Вы создадите 5 проектов для портфолио, даже если до этого никогда не программировали. После обучения — гарантированное трудоустройство.

Понравилась статья?
Да