Найти тему
Nuances of programming

О дивный читаемый код

Оглавление

Источник: Nuances of Programming

Большинство начинающих программистов сталкивается со многими дилеммами в процессе написания кода, например задумываются о том, какой код будет востребован в индустрии. У каждой компании свои бенчмарки, лучшие практики для написания кода и основополагающие принципы. Однако есть один критерий кода, который единодушно поддерживается всеми: читаемость. Читаемый код и проживет дольше, и особых проблем с обслуживанием и пониманием не доставит. Более того, он позволяет будущим поколениям разработчиков легко вносить в него изменения. Проблема читаемости кода актуальна и для начинающих разработчиков Scala. В данной статье мне бы хотелось обратить внимание на ряд часто встречающихся ошибок. 

Для всех наших примеров воспользуемся классом Movie

Обходимся без очень сложных лямбда-функций 

Лямбда очень полезная функция, так как ее можно использовать, не присваивая переменной. Но применение сложных лямбда-функций может вызвать проблемы с читаемостью кода и процессом отладки. Рассмотрим пример, в котором нужно отнести фильм к одной из трех категорий: хороший, плохой, средний. 

Лучше всего обработку этого сложного логического процесса предоставить отдельной функции. Тем самым мы обеспечим читаемость кода для разработчика. 

К тому же в таком виде он гораздо лаконичнее. 

Лучше val, чем var 

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

Эта операция не разрешена компилятором Scala. Чтобы добавить два неизменяемых списка, необходимо создать один новый. 

val updatedActors = movie.actors :+ "Laura Linney"

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

Сопоставление с образцом вместо оператора if/else 

Одной из лучших практик для написании кода в Scala является использование сопоставления с образцом вместо традиционных операторов switch или громоздкого if/else. Создадим рекламные объявления на основе различных жанров кино. 

А теперь попробуем использовать сопоставление с образцом для этой же цели. 

Для подобных случаев такой фрагмент кода гораздо понятнее. Он может использоваться вместо сложной логики if/else. Сопоставление с образцом также применяется с классами case для извлечения значений или других типов. 

Option, Some и None вместо null 

В функции, которая может возвращать значения null, следует использовать тип Option. Предлагаю вам игру в шарады, в которой сначала нужно отгадать количество слов в названии фильма. При использовании простого оператора if/else потребуется каждый раз выполнять явную проверку на null.  

А вот другой способ написания той же функции. Давайте определим movieName как Option[String] вместо String в нашем исходном классе Movie. 

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

Обработка перечислений (enum) при отсутствии значения 

Существует множество способов обработки случая, когда пользователь вводит недопустимое значение enum. Один из них — вернуть null в функцию, выполняющую его поиск. 

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

Подобной функциональности также можно добиться, применив функцию withName.Если случай использования требует раннего обнаружения ошибок в процессе компиляции, то можно рассмотреть вариант с Sealed Traits (запечатанными трейтами), нои у него есть свои ограничения. 

Преобразование с помощью foreach или map

Предположим, что существует метод, который переводит имена актеров фильма с английского на испанский язык (или любой другой). Анонимный метод называется translate.Простой цикл foreach вызовет эту функцию для всех элементов в коллекции movieActors и сохранит содержимое в ListBuffer, так как List не изменяем и не может быть преобразован. 

Как мы видим, foreach пытается изменить внешний список, известный как побочный эффект, который трудно распараллелить. Foreach походит для тех случаев использования, которые включают в себя операции без преобразования коллекции. Давайте используем map для вышеуказанного преобразования. 

def transformFunction(actors: List[String]): List[String] = {
actors.map(translate)
}

Второй способ гораздо лаконичнее и не требует изменения коллекции, существующей вне лямбда-выражения. Map возвращает другой список того же размера, преобразуя каждый его элемент. Таким образом, с точки зрения производительности map определенно лучше, чем foreach. 

Класс case вместо кортежа 

Допустим, мы хотим порекомендовать названия фильмов на основании зрительского рейтинга. В качестве примера возьмем List ((Таинственная река), 8.0), (Властелин колец), 8.9)). Кортеж Scala как раз и существует для таких операций, которые выполняют роль небольшого контейнера для доступа к индивидуальным элементам.

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

Добавление новых полей происходит легче, если мы используем класс case, а не кортеж. 

Интерполяция или конкатенация строк 

Возвращаясь к нашей игре в шарады, предположим, что кто-то отгадал первую половину названия фильма, такого как “Властелин колец”. Полное название может быть создано с помощью конкатенации строк, в результате чего возникнет новая строка, использующая оператор +. То, как компилятор обрабатывает ошибки в случае конкатенации строк, понять не так-то просто. 

def guessMovie(firstName: String, lastName: String): String = {
firstName + " " + lastName
}

Объединяя что-либо со String, следует рассмотреть вариант использования интерполяции строк. Он более удобный, безопасный, последовательный и читаемый. 

def guessMovie(firstName: String, lastName: String): String = {
s"$firstName $lastName"
}

Производительность конкатенации строк и интерполятора (s, f и raw) может варьироваться в зависимости от длины строки. 

Заключение 

И это всего лишь начало. Читаемость кода — предмет бесконечных обсуждений в мире программирования. Некоторые могут утверждать, что код следует снабжать хорошими комментариями, чтобы другие разработчики лучше его понимали. Я же уверена, что наши небольшие действия, такие как соблюдение надлежащих правил именования переменных, поддержание межстрочного интервала, уменьшение логической сложности и следование выше рассмотренным практикам, несомненно приведет к более читаемому коду.

Ссылки 

https://github.com/lloydmeta/enumeratum/blob/master/enumeratum-core/src/main/scala/enumeratum/Enum.scala

https://stackoverflow.com/questions/33593525/scala-safe-way-of-converting-string-to-enumeration-value

https://stackoverflow.com/questions/28319064/java-8-best-way-to-transform-a-list-map-or-foreach

https://nrinaudo.github.io/scala-best-practices/tricky_behaviours/string_concatenation.html

Читайте также:

Читайте нас в телеграмме и vk

Перевод статьи Niharika Gupta: Readable code is better code