Если вы пришли сюда из моей статьи по программированию на Swift о структуре и удобочитаемости, вы уже знаете некоторые основы. Если нет, ничего страшного. Я собираюсь перейти к нескольким из упомянутых тем и подробнее остановиться на них здесь. Я рекомендую вам хотя бы просмотреть статью выше, чтобы получить несколько быстрых советов.
Прежде всего, мой основной язык - Swift, за ним идут Python, COBOL, C # / Java, затем C / C ++. Я уверен, что эти советы все равно применимы, даже если вы используете другой язык.
Формат этой серии будет следующим:
1. Переменные
2. Структуры данных
3. Функции
4. Классы / структуры
5. Файловая структура < br /> 6. Структура проекта
Мы начнем с самых простых способов сохранить ваш код чистым и расширяться.
Обзор
В какой-то момент вашей карьеры программиста вы могли встретить программу, которая выглядит примерно так:
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int n1,n2,s; n1=atoi(argv[1]); n2=atoi(argv[2]); s=n1+n2; printf("%d\n",s); return 0; }
Как долго вам пришлось смотреть на эту программу, чтобы понять, что она делает? Наверное, второй правильно взял?
Если вы никогда не изучали C, atoi
- это функция, преобразующая строку ASCII в целое число. Остальное должно быть довольно знакомо.
Давайте еще раз посмотрим на этот пример.
// add.c - takes two integers passed as arguments and prints the sum #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int first_number = atoi(argv[1]); int second_number = atoi(argv[2]); int sum = first_number + second_number; printf("%d\n", sum); }
Видите, насколько это легче понять? Вы можете просто взглянуть на него и понять, что он делает. Так что же изменилось? Это то, что мы будем освещать в этих темах.
Переменные
Много раз я просматривал код и наталкивался на что-то вроде s=n%k
. Меня не так беспокоит отсутствие пробелов, но меня беспокоит то, что s
должно быть. Чтобы знать это, нам нужно знать, что такое n
и k
. Это ужасный дизайн.
Что еще хуже, когда первоначальный разработчик собирается изменить эту программу позже с другой переменной, похожей на s
, они используют s1
. Видите ли вы, где это может быть проблемой, когда у вас есть много переменных с очень похожими именами, которые могут означать очень разные вещи?
При написании имен переменных в Swift я обычно использую описательные имена. Я использую самое короткое имя, которое точно описывает, что это такое. Это может быть так же просто, как sum
в локальной области видимости, но в более широком контексте я мог бы использовать total
или sumOfAllCars
. Для python я бы использовал snake_case
вместо camelCase
, поскольку он хорошо интегрируется во внешние библиотеки.
Соглашения об именах должны хорошо работать с языком, а не только с вашей программой. Всегда хорошо понимайте, как другие разработчики записывают свои переменные на вашем языке и отражают это, но будьте наглядными. Никто не любит рассматривать sum_ra_EE
как имя переменной. В этом нет смысла. Кто бы мог подумать, что EE
это модуль, которому он принадлежит? А что такое ra
? Мне пришлось бы просмотреть почти половину всей вашей кодовой базы, чтобы точно понять, что означает ra
. Если бы это был общедоступный интерфейс к моей библиотеке, он бы отпугнул многих разработчиков просто потому, что он не интуитивно понятен.
Чисто читаемый полезный код = счастливые разработчики = больше звезд на github = больше поддержки = больше использования = больше пожертвований. А кто не любит деньги?
Поэтому, называя переменные, сделайте их понятными для тех, кто впервые смотрит на ваш код. Предоставьте некоторое представление о назначении этой переменной в имени.
Вернемся к первому примеру выше int n1,n2,s;
. На первый взгляд, это выглядит довольно понятно, и в простых программах, если честно, все было прекрасно, как было. Но по мере того, как ваша программа становится больше, мы получаем n3...n47
. Если вы хотите использовать переменную в будущем, вы не хотите, чтобы прокручивалась страница, спрашивая «Что было n26
?». Вот почему так важно правильное именование. Именно здесь вы должны разбить это на части, чтобы сгруппировать вместе переменные, принадлежащие одной и той же задаче. Например, следующее.
... int car_mileage, next_oilchange_mileage, oilchange_count; date next_oilchange_date; double rent_amount; date rent_due_date; int rent_total_payments; ...
Для логических значений используйте is
, did
, had
или другой предлог, чтобы указать, что это истинное или ложное утверждение. isEligible
- хороший идентификатор предложения, он лучше, чем eligible
. eligible
в некоторых случаях может означать нечто большее, чем просто логическое значение.
Для чисел используйте total
, count
, square_foot
или что-то, что может определять итеративное значение.
Для строк и символов попробуйте использовать что-то, что не является итеративным или предложным.
Структуры данных
Структуры данных, массивы, словари, наборы обычно используются во многих языках. Как и переменные, они должны иметь описательные имена.
При работе с массивами вы почти всегда захотите использовать имя во множественном числе, например animals = ['dog', 'cat', 'bird', 'fish']
или carMakes = ['audi', 'bmw', 'chevrolet', 'ford', 'mercedes benz', 'nissan', 'toyota']
.
Имена должны описывать то, что содержит массив. Это не имеет смысла, если вы читаете цикл, который выглядит так:
for item in arr: do_something_with(item)
Вместо этого нам следует обдумать, как это записать. Используя массив animals
выше, мы могли бы переписать это, чтобы показать намерение, как мы хотим это записать.
for animal_type in animals: do_something_with(animal_type)
Словари и наборы также должны быть множественного числа, когда вы видите animal_names
, вы ожидаете, что несколько элементов будут принадлежать словарю. Используя описательное имя, вы можете точно предположить, что может принадлежать словарю: animal_names["dog"]
Когда вы используете массивы, словари и наборы, вы должны помнить о том, как вы называете их, чтобы они выглядели и звучали уместно, когда вы будете использовать их позже в своем коде. Плохое именование ведет к плохой читаемости.
При написании коллекций, которые будут содержать элементы на сумму более 80 символов, более полезно разделить каждый элемент на отдельной строке.
names = {'Alice', 'Bob', 'Charlie', 'Doug', 'Edward', 'Frank', 'George', 'Harry', 'Ingrid', 'James'} // Might look better as names = {'Alice, 'Bob', 'Charlie, 'Doug', 'Edward', 'Frank', 'George', Harry', 'Ingrid', 'James'} // Or even better as names = { 'Alice', 'Bob', 'Charlie', 'Doug', 'Edward', 'Frank', 'George', 'Harry', 'Ingrid', 'James' }
Функции
Правильное именование функций - еще один способ сделать ваш код более читабельным. Называть функции следует в соответствии с их назначением. Я знаю, что в своих заполнителях я обычно использую do_stuff()
, но это потому, что мне все равно, что будет дальше, в конце концов, это всего лишь пример. Но когда мне все равно, я использую описательные имена.
Я использовал изображение машины выше, но меня действительно волнует только то, как вы заводите машину. Когда я сажусь в машину, первое, что я делаю, это ищу гнездо зажигания, чтобы завести машину. Когда я впервые использую новую библиотеку, я ищу функцию, которая поможет мне начать работу с библиотекой. Иногда это так же просто, как инициализировать класс, поставляемый с библиотекой. Но что, если он преобразует дату в формат ISO 8601 (yyyy-MM-ddThh:mm.ssZ
)?
string dateToISO8601String(Date date) { ... } myDateString = dateToISO8601String(myDate);
Это показывает намерение выполнять функцию. Он принимает дату, и по имени функции мы можем определить, что все, что передается, приведет к форматированной строке ISO 8601, полезной для большинства журналов.
На этом этапе мы беспокоимся не только об именах, но и о структуре проекта в целом, поскольку функции отображаются на корневом уровне во всех основных файлах кода.
Некоторые языки позволяют размещать функции на корневом уровне вашего проекта. В этих языках обычно есть одно требование: функция должна быть определена / заглушена, прежде чем ее можно будет использовать.
В C мы могли бы сделать что-то вроде этого:
#include <stdio.h> #include <stdlib.h> int add(int a, int b) { return a + b; } int main(int argc, char *argv[]) { int n1, n2, sum; n1 = atoi(argv[1]); n2 = atoi(argv[2]); sum = add(n1, n2); printf("%d", sum); }
Или с заглушкой:
#include <stdio.h> #include <stdlib.h> int add(int a, int b); int main(int argc, char *argv[]) { int n1, n2, sum; n1 = atoi(argv[1]); n2 = atoi(argv[2]); sum = add(n1, n2); printf("%d", sum); } int add(int a, int b) { return a + b; }
В Python:
import sys def add(a, b): return a + b n1 = int(sys.argv[1]) n2 = int(sys.argv[2]) sum = add(n1, n2) print(str(sum))
Теперь я знаю, что использовал a
и b
в методе, но он настолько прост и универсален, что нам все равно, что такое a
и b
, мы просто хотим, чтобы они были числовыми и складывались вместе два значения.
Вы могли заметить, что я поместил две строки между строками import
и include
и началом тела программы. Я начал делать это после того, как изучил Python. Согласно стандарту PEP 8 между операторами импорта и остальной частью вашего кода должно быть две строки. Он просто выглядит чище и помогает поставить некоторый промежуток между требованиями и логикой.
Я также поставил пробел между main
и add
, потому что они не связаны. Несмотря на то, что main
в конечном итоге вызывает функцию add
, add
отвечает за сложение двух чисел, а main
отвечает за сбор аргументов и отображение результата на экране.
Наконец, я добавил пространство между каждым логическим разделом внутри файла main. Сбор аргументов - это раздел, который я сгруппировал вместе. Обе линии выполняют почти одно и то же действие. У меня sum
сам по себе. Пока мы создаем экземпляр переменной, на самом деле мы делаем это на основе результата функции. Это не то же самое, что и выше, поэтому мы добавляем немного места, чтобы отметить разницу. Наконец, print работает сам по себе, потому что он не имеет ничего общего со сбором аргументов или получением результата из функции.
Когда вы видите это в реальном мире, у вас может быть 40 строк, сгруппированных вместе, после прочтения первых 3 вы создаете шаблон, вы переходите к последней записи и видите, что это тот же образец, и вы предполагаете, что все остальные 36 строк являются точно так же. Вы пришли к выводу, что все эти 40 строк отвечают за аналогичную задачу.
В вашем собственном коде это нормально, но я хочу вас предупредить, что при использовании базы кода из менее известного источника я предлагаю вам просмотреть все 40 записей, чтобы убедиться, что он делает именно то, что вы подозреваете. Легко вставить что-то, что может привести к другим результатам, чем вы ожидали.
Резюме
Это всего лишь скачок к структуре и удобочитаемости, в моих следующих примерах будут использоваться реальные проекты, чтобы показать, как я все разбил. Я также сделаю ссылку на пару проектов с открытым исходным кодом, чтобы показать примеры чистого кода.
Если вы хотите взглянуть на один, который может быть ошеломляющим, но все же имеет хорошую структуру, взгляните на исходный код netcat 1.10. Это может дать вам хорошее представление о том, почему так важны удобочитаемость, структура и хорошо названные переменные.
В следующей статье мы рассмотрим классы и структуры.