Статьи

Написание элегантного и удобочитаемого кода

В этом уроке мы дадим вам девять практических приемов написания элегантного и удобочитаемого кода. Мы не будем говорить о конкретных архитектурах, языках или платформах. Основное внимание уделяется написанию лучшего кода. Давайте начнем.

«Измерение прогресса в программировании по строкам кода похоже на измерение прогресса в самолетостроении по весу». — Билл Гейтс

Если вы разработчик, то, возможно, были времена, когда вы писали код, и через несколько дней, недель или месяцев вы оглядывались на него и спрашивали себя: «Что делает этот фрагмент кода?» Ответ на этот вопрос мог быть «Я действительно не знаю!» В этом случае единственное, что вы можете сделать, — это пройти код от начала до конца, пытаясь понять, о чем вы думали, когда писали его.

В основном это происходит, когда мы ленивы, и мы просто хотим реализовать ту новую функцию, которую просил клиент. Мы просто хотим выполнить работу с минимальными усилиями, насколько это возможно. И когда он работает, мы не заботимся о самом коде, потому что клиент никогда не увидит ужасную правду, не говоря уже о ее понимании. Правильно? Неправильно. В наши дни совместная работа над программным обеспечением стала по умолчанию, и люди будут видеть, читать и проверять код, который вы пишете. Даже если ваши коллеги не проверяют ваш код, вы должны сделать привычкой писать понятный и читаемый код. Всегда.

Большую часть времени вы не работаете один над проектом. Мы часто видим уродливый код с переменными, имеющими такие имена, как i , a , p , pro и rqs . И если это действительно становится плохо, этот шаблон виден во всем проекте. Если это звучит знакомо, то я почти уверен, что вы задали себе вопрос: «Как этот человек может написать такой код?» Конечно, это делает вас еще более благодарными, когда вы сталкиваетесь с понятным, читаемым и даже красивым кодом. Четкий и понятный код можно прочитать за считанные секунды, и он может сэкономить вам и вашим коллегам много времени. Это должно быть вашей мотивацией для написания качественного кода.

Мы все согласны с тем, что код должен быть легким для понимания. Правильно? Первый пример фокусируется на расстоянии. Давайте посмотрим на два примера.

1
return gender == «1» ?
1
2
3
4
5
if(gender == «1»){
  return weight * (height / 10);
} else {
  return weight * (height * 10);
}

Хотя результаты этих примеров идентичны, они выглядят совершенно по-разному. Зачем вам использовать больше строк кода, если вы можете писать меньше? Давайте рассмотрим два других примера, которые, я уверен, вы часто видите

1
2
for (Node* node = list->head; node != NULL; node = node->next)
  print(node->data);
01
02
03
04
05
06
07
08
09
10
Node* node = list->head;
 
if(node == NULL) return;
 
while(node->next != NULL) {
  Print(node->data);
  node = node->next;
}
 
if(node != NULL) Print(node->data);

Опять же, результат этих примеров идентичен. Какой из них лучше? И почему? Чем меньше строк кода, тем лучше код? Мы вернемся к этому вопросу позже в этом уроке.

В информатике вы часто слышите фразу «меньше значит больше». Вообще говоря, если вы можете решить проблему меньшим количеством строк кода, тем лучше. Вероятно, вам понадобится меньше времени, чтобы понять класс из 200 строк, чем класс из 500 строк. Однако всегда ли это так? Взгляните на следующие примеры.

1
reservation((!room = FindRoom(room_id))) ||
1
2
3
room = FindRoom(room_id);
if(room != NULL)
  reservation(!room->isOccupied());

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

1
2
3
4
5
6
// Determine where to spawn the monster along the Y axis
CGSize winSize = [CCDirector sharedDirector].winSize;
int minY = monster.contentSize.width / 2;
int maxY = winSize.width — monster.contentSize.width/2;
int rangeY = maxY — minY;
int actualY = (arc4random() % rangeY) + minY;

Выбор описательных имен для таких вещей, как переменные и функции, является ключевым аспектом написания читаемого кода. Это поможет вам и вашим коллегам быстро понять код. Присвоение имени переменной tmp не говорит вам ничего, кроме того, что переменная по какой-то причине является временной, что является не более чем обоснованным предположением. Он не сообщает, хранит ли переменная имя, дату и т. Д.

Еще один прекрасный пример — это название метода stop . Это не плохое имя само по себе, но это действительно зависит от реализации метода. Если он выполняет опасную операцию, которую нельзя отменить, вы можете переименовать его, чтобы kill или pause если операция может быть возобновлена. Вы поняли идею?

Если вы работаете с переменной для веса картофеля, почему вы назвали бы ее tmp ? Когда вы вернетесь к этому коду через несколько дней, вы не вспомните, для чего используется tmp .

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

1
2
3
tmp = first_potato;
first_potato = second_potato;
second_potato = tmp;

В приведенном выше примере tmp описывает, что он делает, он временно сохраняет значение. Он не передается функции или методу и не увеличивается и не изменяется. Он имеет четко определенное время жизни, и ни один опытный разработчик не будет отброшен именем переменной. Иногда, однако, это просто лень. Посмотрите на следующий пример.

1
2
3
4
5
NSString *tmp = user.name;
tmp += » » + user.phone_number;
tmp += » » + user.email;
[template setObject:tmp forKey:@»user_info»];

Если tmp хранит информацию о пользователе, почему она не называется userInfo ? Правильное именование переменных, функций, методов, классов и т. Д. Важно при написании читаемого кода. Это не только сделает ваш код более читабельным, но и сэкономит ваше время в будущем.

Objective-C довольно многословен, но его очень легко прочитать. Apple использует четко определенное соглашение об именах, которое вы можете использовать в большинстве языков программирования. Вы можете прочитать больше об этом соглашении об именах в Программирование с Objective-C .

Как мы видели в предыдущем совете, важно правильно выбирать имена. Однако не менее важно добавить смысл к именам, которые вы используете для переменных, функций, методов и т. Д. Это не только помогает избежать путаницы, но и облегчает понимание написанного вами кода. Выбор имени, которое имеет смысл, почти как добавление метаданных к переменной или методу. Выбирайте описательные имена и избегайте общих. Например, слово « add не всегда идеально, как вы можете видеть в следующем примере.

1
2
3
bool addUser(User u){
}

Непонятно, что должен делать addUser . Добавляет ли он пользователя в список пользователей, в базу данных или в список людей, приглашенных на вечеринку? Сравните это с registerUser или signupUser . Это имеет больше смысла. Правильно? Взгляните на следующий список, чтобы лучше понять, к чему мы клоним.

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

Многим программистам не нравятся длинные имена, потому что их трудно запомнить и сложно набрать. Конечно, имя не должно быть смехотворно длинным, как newClassForNavigationControllerNamedFirstViewController . Это трудно запомнить, и он просто делает ваш код уродливым и нечитаемым.

Как мы видели ранее, короткие имена наоборот тоже не годятся. Каков правильный размер для имени переменной или метода? Как вы решаете между именованием переменной len , length или user_name_length ? Ответ зависит от контекста и сущности, к которой привязано имя.

Длинные имена больше не являются проблемой при использовании современной IDE (Integrated Development Environment). Завершение кода помогает вам избежать опечаток, а также делает предложения, чтобы облегчить запоминание имен.

Вы можете использовать короткие (er) имена, если переменная является локальной. Более того, рекомендуется использовать более короткие имена для локальных переменных, чтобы ваш код читался. Посмотрите на следующий пример.

1
2
NSString *link = [[NSString alloc] initWithFormat:@»http://localhost:8080/WrittingGoodCode/resources/GoodCode/getGoodCode/%@»,idCode];
NSURL *infoCode = [NSURL URLWithString:link];

Булево имя может быть сложно назвать, потому что оно может иметь различное значение в зависимости от того, как вы читаете или интерпретируете имя. В следующем фрагменте кода read_password может означать, что пароль был прочитан программой, но это также может означать, что программа должна прочитать пароль.

1
BOOL readPassword = YES;

Чтобы избежать этой проблемы, вы могли бы переименовать вышеуказанное логическое значение в didReadPassword чтобы указать, что пароль был прочитан, или shouldReadPassword чтобы показать, что программе необходимо прочитать пароль. Это то, что вы часто видите в Objective-C, например.

Добавление комментариев к коду важно, но не менее важно использовать их экономно. Они должны использоваться, чтобы помочь кому-то понять ваш код. Чтение комментариев, однако, также требует времени, и если комментарий не приносит особой пользы, то это время теряется. Следующий фрагмент кода показывает, как не использовать комментарии.

01
02
03
04
05
06
07
08
09
10
// This happens when memory warning is received
— (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
 
// This validate the fields
-(BOOL)validateFields {
 
}

Эти фрагменты кода полезны для вас? Ответ, вероятно, «Нет» Комментарии в вышеприведенных примерах не добавляют дополнительную информацию, тем более что имена методов уже очень описательны, что часто встречается в Objective-C. Не добавляйте комментарии, которые объясняют очевидное. Посмотрите на следующий пример. Разве это не намного лучше использовать комментарии?

1
2
3
4
5
// Determine speed of the monster
int minDuration = 2.0;
int maxDuration = 8.0;
int rangeDuration = maxDuration — minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;

Подобные комментарии позволяют очень легко и быстро перемещаться по кодовой базе. Это избавляет вас от необходимости читать код и помогает понять логику или алгоритм.

У каждого языка или платформы есть (или более) руководство по стилю, и даже у большинства компаний есть такое. Вы помещаете фигурные скобки метода Objective-C на отдельной строке или нет?

1
2
3
— (void)calculateOffset {
 
}
1
2
3
4
— (void)calculateOffset
{
 
}

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
— (BOOL)saveToImage:(UIImage *)image withFileName:(NSString *)fileName {
    BOOL result = NO;
    NSString *documents = nil;
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
 
    if (paths.count) {
        documents = [paths objectAtIndex:0];
        NSString *basePath = [documents stringByAppendingPathComponent:@»Archive»];
 
        if (![[NSFileManager defaultManager] fileExistsAtPath:basePath]) {
            NSError *error = nil;
            [[NSFileManager defaultManager] createDirectoryAtPath:basePath withIntermediateDirectories:YES attributes:nil error:&error];
 
            if (!error) {
                NSString *filePath = [basePath stringByAppendingPathComponent:fileName];
                result = [UIImageJPEGRepresentation(image, 8.0) writeToFile:filePath atomically:YES];
 
            } else {
                NSLog(@»Unable to create directory due to error %@ with user info %@.», error, error.userInfo);
            }
        }
    }
 
    return result;
}

Если единица кода пытается сделать слишком много, вы часто получаете глубоко вложенные условные операторы, множество проверок на ошибки и слишком сложные условные операторы. Этот метод выполняет три действия: извлекает путь к каталогу документов приложения, извлекает и создает путь к каталогу архивов и записывает образ на диск. Каждое задание можно поставить своим собственным методом, как показано ниже.

01
02
03
04
05
06
07
08
09
10
— (BOOL)saveToImage:(UIImage *)image withFileName:(NSString *)fileName {
    NSString *archivesDirectory = [self applicationArchivesDirectory];
    if (!archivesDirectory) return NO;
 
    // Create Path
    NSString *filePath = [archivesDirectory stringByAppendingPathComponent:fileName];
 
    // Write Image to Disk
    return [UIImageJPEGRepresentation(image, 8.0) writeToFile:filePath atomically:YES];
}
1
2
3
4
— (NSString *)applicationDocumentsDirectory {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    return paths.count ?
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
— (NSString *)applicationArchivesDirectory {
    NSString *documentsDirectory = [self applicationDocumentsDirectory];
    NSString *archivesDirectory = [documentsDirectory stringByAppendingPathComponent:@»Archives»];
 
    NSFileManager *fm = [NSFileManager defaultManager];
 
    if (![fm fileExistsAtPath:archivesDirectory]) {
        NSError *error = nil;
        [fm createDirectoryAtPath:archivesDirectory withIntermediateDirectories:YES attributes:nil error:&error];
 
        if (error) {
            NSLog(@»Unable to create directory due to error %@ with user info %@.», error, error.userInfo);
            return nil;
        }
    }
 
    return archivesDirectory;
}

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

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