Skip to content

Latest commit

 

History

History
40 lines (32 loc) · 9.23 KB

issues.md

File metadata and controls

40 lines (32 loc) · 9.23 KB

Рекомендации по написанию кода

Стайл-гайд

  1. (S1) Следуейте рекомендациям для выбранного языка программирования: F#, Haskell, Scala. Можете использовать и другие гайд-лайны, указывая на них ссылку в шапке вашего решения. То же требуется и для других языков.
  2. (S1) В Haskell, Scala и F# принят camelCase / PascalCase, в Lisp — lisp-case. В camelCase чаще всего пишут функции (кроме конструкторов типов и активных шаблонов), а в PascalCase — типы.
  3. (S1) Не забывайте пробелы вокруг бинарных операторов, внутри конструкторов списка и между функцией и аргументами.
  4. (S1) Обычно лучше не писать скобки, если их можно опустить.
  5. (S1) Старайтесь давать функциям, особенно для top level, осмысленные имена.
  6. (S2) Не нужно указывать типы функций, если код и так компилируется (для F#): лучше написать такой код и дать переменным такие имена, чтобы тип легко считывался программистами. Синтаксически в F# типы сильно перегружают код и скорее усложняют его чтение.
  7. (S2) Не нужно указывать типы .NET-стиле: например, вместо List<'a> или list<'a> лучше писать 'a list. Допустимые исключения: Lazy<'t>.

Лаконичность

  1. (L1) Не оставляйте выражения, которые можно вычислить тривиально: cond && true, cond && false, if cond then true else false, cond = true, cond = false, if cond1 then true else cond2 и так далее.
  2. (L1) Лямбда-функция fun x -> f x, где f — просто какая-то функция, упрощается до f.
  3. (L1) Предпочитайте паттерн-матчинг условным выражениям.
  4. (L2) Не забывайте, что паттерн-матчинг по последнему аргументу функции можно переписать через function. Более того, обыычно аргумент, по которому идет сопоставление (как правило, это типы-контейнеры) намеренно ставят на последнее место; не только чтобы можно было писать function, но и чтобы было удобнее использовать каррирование: так, например, можно очень просто определить функцию частичных сумм List.fold (+) 0.
  5. (L2) Помните, что в паттерн-матчинге есть guard'ы (условия when). Кроме того, Вложенный паттерн-матчинг или паттерн-матчинг, в котором каждая ветка разворачивается в условное выражение, чаще всего сокращается до простого паттерн-матчинга с guard'ами.
  6. (L2) Запись if count > 0 then foo () читается заметно проще, чем такая или аналогичная через function:
    match count with
    | 0 -> ()
    | _ -> foo ()
  7. (L3) Если часть шаблона не используется, заменяйте ее на символ _. Кроме того, рекомендую подбирать имена для шаблонов-констант таким образом, чтобы они не перекрывали имена аргументов функций (добавляйте штрих '). Если же вы, скажем, сопоставляете n c n, то можно заменить шаблон на _, а в правой части использовать n из аргументов.
  8. (L4) Нередко встречается такая конструкция: объявление списка, затем новая константа, где к этому списку применили какую-то одну функцию и так далее. Вместо это лучше реализовать то же самое в виде конвейера: передавать список в одну функцию, затем сразу же результат в третью и так далее; для этого есть операторы |> и <|. Но здесь нужно следить за балансом: каждая из функций должна быть достаточно простой, чтобы код легко считывался. Если этого не происходит — нужны константы с понятными именами для промежуточных вычислений.
  9. (L4) Как правило, конвейер лучше читается, если его записать по строчно, начиная с |> каждую функцию. Исключения допустимы, если весь конвейер помещается в одну строку (пусть 80 символов, а лучше меньше).

Эффективность

  1. (E1) Каждый раз при написании рекурсивной функции подумайте, получилась ли рекурсия хвостовой и можно ли ее такой сделать. Если можно, то лучше сделать.
  2. (E2) Старайтесь не использовать в рекурсии конкатенацию списков, если второй список имеет константную длину (чаще всего это один элеимент). Конкатенация работает за линию от длины первого списка, значит, вы получаете потенциальный квадрат в асимптотике. Проще добавлять элементы в начало списка, а в конце сделать за линию разворот списка.
  3. (E3) Не используйте List.item там, где можно обойтись обходом списка. List.item имеет линейную сложность, поэтому, скорее всего, если индекс растет с каждой итерацией, сложность вашего решения умножается на одну линию. Кроме того, вместо даже List.item 0 лучше писать List.head. Первый вариант формально имеет линейную сложность, второй — константную (и он компактнее и идейно более правильный). Поэтому при беглом анализе асимптотики в коде с List.head возникает меньше вопросов.

Дизайн

  1. (D1) The Don't Repeat Yourself Principle. Не допускается дублирование логики. Его можно ликвидировать с дополнительной абстракции. В нашем случае почти всегда это новая функция.
  2. (D2) Функция, объявленная во внешней области видимости, доступна при импорте всем пользователям. Поэтому если у нее среди аргументов есть аккумулятор, пользователю придется угадывать, как функция реализована, чтобы подставить правильное значение в этот аргумент. Аккуратнее будет написать вложенную функцию, которая имеет аккумуляторы, а во внешней оставить только полезные для интерфейса аргументы и вызов функции-реализации.
  3. (D3) Не пишите излишне абстрактный код. Каждая функция должна иметь конкретную задачу: как-то обработать коллекцию, что-то посчитать и так далее. Пример плохой функции: forLoop с семинара. По ней функции непонятно, как ей пользоваться, она больше похожа на синтаксическую конструкцию языка, чем на собственно функцию (так и была придумана: показать, как делать циклы).