Контекст
Нещодавно мене зацікавило створення кращого форматованого коду. І в кращому випадку я маю на увазі "дотримання правил, схвалених достатньою кількістю людей, щоб вважати це хорошою практикою" (оскільки, звичайно, ніколи не буде єдиного "найкращого" способу кодування).
Цього дня я здебільшого кодую в Ruby, тому я почав використовувати лінійку (Rubocop), щоб надати мені деяку інформацію про "якість" мого коду (ця "якість" визначається проектом у стилі ruby-style-guide ).
Зверніть увагу , що я буду використовувати «якість» , як в «якості форматування", не стільки про ефективність коди, навіть якщо в деяких випадках ефективність коди на насправді це залежить від того, як написаний коду.
У будь-якому випадку, зробивши все це, я зрозумів (або принаймні згадав) кілька речей:
- Деякі мови (особливо Python, Ruby тощо) дозволяють робити великі однолінійки коду
- Дотримуючись деяких вказівок щодо вашого коду, можна зробити його значно скороченим і все ще дуже зрозумілим
- Однак занадто суворе дотримання цих вказівок може зробити код менш зрозумілим / легким для читання
- Код може майже повністю дотримуватися деяких вказівок і все ще бути низької якості
- Читання коду здебільшого суб'єктивне (як, наприклад, "те, що мені здається, може бути абсолютно незрозумілим для іншого розробника")
Це лише спостереження, а не абсолютні правила. Ви також зауважите, що читабельність коду та наступні вказівки можуть здатися непов'язаними на даний момент, але тут ці вказівки - це спосіб зменшити кількість способів переписати один фрагмент коду.
Тепер кілька прикладів, щоб зробити все це більш зрозумілим.
Приклади
Візьмемо простий випадок використання: у нас є додаток з " User
" моделлю. Користувач має необов'язкову firstname
і surname
обов'язкову email
адресу.
Я хочу написати метод, який " name
" поверне тоді ім'я ( firstname + surname
) користувача, якщо принаймні його firstname
або surname
є, або його email
як резервне значення, якщо ні.
Я також хочу, щоб цей метод приймав use_email
параметр " " як булевий, що дозволяє використовувати електронну пошту користувача як резервне значення. Цей use_email
параметр має бути за замовчуванням (якщо він не переданий) як " true
".
Найпростіший спосіб записати це в Ruby:
def name(use_email = true)
# If firstname and surname are both blank (empty string or undefined)
# and we can use the email...
if (firstname.blank? && surname.blank?) && use_email
# ... then, return the email
return email
else
# ... else, concatenate the firstname and surname...
name = "#{firstname} #{surname}"
# ... and return the result striped from leading and trailing spaces
return name.strip
end
end
Цей код - найпростіший і легкий для розуміння спосіб зробити це. Навіть для того, хто не «говорить» Рубі.
Тепер спробуємо зробити це коротшим:
def name(use_email = true)
# 'if' condition is used as a guard clause instead of a conditional block
return email if (firstname.blank? && surname.blank?) && use_email
# Use of 'return' makes 'else' useless anyway
name = "#{firstname} #{surname}"
return name.strip
end
Це коротше, все ж легко зрозуміти, якщо не простіше (захисний пункт читається більш природно, ніж умовний блок). Застереження про охорону також робить його більш відповідним вказівкам, які я використовую, тому тут безпрограшно. Ми також знижуємо рівень відступу.
Тепер скористаємося магією Ruby, щоб зробити її ще коротшою:
def name(use_email = true)
return email if (firstname.blank? && surname.blank?) && use_email
# Ruby can return the last called value, making 'return' useless
# and we can apply strip directly to our string, no need to store it
"#{firstname} #{surname}".strip
end
Навіть коротше та досконало дотримуючись вказівок ... але набагато менш зрозуміло, оскільки відсутність заяви про повернення робить це трохи заплутаним для тих, хто не знайомий з цією практикою.
Саме тут ми можемо почати задавати питання: чи дійсно воно того варте? Якщо ми скажемо "ні, зробіть це читабельним і додайте return
" "(знаючи, що це не буде поважати вказівки). Або ми повинні сказати "Це добре, це Рубін шлях, вивчи прокляту мову!"?
Якщо ми беремо варіант B, то чому б не зробити його ще коротшим:
def name(use_email = true)
(email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end
Ось він, однолінійний! Звичайно, це коротше ... тут ми скористаємося тим, що Ruby поверне значення або інше залежно від того, яке визначено (оскільки електронна пошта буде визначена за тієї ж умови, що і раніше).
Ми також можемо написати це:
def name(use_email = true)
(email if [firstname, surname].all?(&:blank?) && use_email) || "#{firstname} #{surname}".strip
end
Це коротко, не так важко читати (я маю на увазі, ми всі бачили, як може виглядати потворний однокласник), хороший Рубі, він відповідає правилам, якими я користуюся ... Але все ж, порівняно з першим способом написання це, набагато менш легко читати та розуміти. Можна також стверджувати, що цей рядок занадто довгий (більше 80 символів).
Питання
Деякі приклади коду можуть показати, що вибір між "повнорозмірним" кодом та багатьма його зменшеними версіями (аж до відомого однолінійного) може бути важким, оскільки, як ми бачимо, однолінійки можуть бути не такими страшними, але все-таки ніщо не переможе "повнорозмірний" код з точки зору читабельності ...
Тож ось справжнє питання: де зупинитися? Коли короткий, досить короткий? Як дізнатися, коли код стає "занадто коротким" та менш читабельним (маючи на увазі, що він досить суб'єктивний)? І навіть більше: як завжди кодувати відповідно і уникати змішування одноклассників з "повнорозмірними" фрагментами коду, коли мені просто подобається?
TL; DR
Основне питання тут: коли потрібно вибрати між "довгим, але чітким, читабельним і зрозумілим фрагментом коду" та "потужним, коротшим, але важче читати / розуміти однолінійку", знаючи, що ці два - це верх і внизу шкали, а не два єдині варіанти: як визначити, де знаходиться межа між "достатньо чітким" та "не таким чітким, як має бути"?
Головне питання - це не класичний "Одноколісні та читальні: який краще?" але "Як знайти баланс між цими двома?"
Редагуйте 1
Коментарі в прикладах коду мають бути «ігноровані», вони є тут, щоб уточнити, що відбувається, але не повинні враховуватися при оцінці читабельності коду.
return
доданим ключовим словом . Ці сім символів додають моїх очей трохи ясності.
[firstname,surname,!use_email].all?(&:blank?) ? email : "#{firstname} #{surname}".strip
… тому що false.blank?
повертається правда, а потрійний оператор економить вам кілька символів ... ¯ \ _ (ツ) _ / ¯
return
слід додати ключовому слову ?! Він не надає жодної інформації . Це чистий безлад.