Для любого программиста важно знать, когда необходимо написать комментарий в коде, а когда лучше обойтись без него, а также, если вы решили написать его, сделать это с минимально возможным количеством слов, прямо к делу, что поможет другим коллегам понять, что происходит в считанные секунды.
На мой взгляд, в этой области есть две большие ошибки: первая — считать, что комментировать все или почти все — это хорошо, тогда как на самом деле мы создаем избыток лишней информации. Вторая — думать, что контекста всегда достаточно для понимания кода и что комментарии никогда не следует писать, что приводит к появлению частей кода, когнитивная сложность которых достаточно высока, чтобы оправдать комментарий, снижающий эту сложность. По этой причине я считаю, что, как говорил Аристотель, добродетель лежит посередине.
Но прежде чем углубиться в эту тему, мы рассмотрим базовые и синтаксические основы написания комментариев в Dart, основываясь на руководстве Effective Dart.
📽 Видеоверсия доступна на YouTube и Odysee
Пишите хорошую документацию
В разделе документации мы можем увидеть подробное объяснение того, как писать комментарии.
В этом разделе объясняется, что для написания простых комментариев, которые мы не хотим включать в сгенерированную документацию, например, объяснения того, что делает строка кода в методе, мы будем использовать двойной слэш //
, а для комментариев, которые объясняют, что делает метод, или для чего нужно свойство, или что делает класс (эти три группы должны быть в сгенерированной документации), мы будем использовать тройной слэш //
.
/// Saves the given [user] if all its mandatory parameters are set.
Future<void> saveUser(User user) async {
final messenger = ScaffoldMessenger.of(context);
late String message;
// Due to a db restriction, check if the name is set before saving.
if (user.name != null) {
await database.persistUser(user: user);
message = 'User successfully saved';
} else {
message = 'You must set a name for the user before saving it.';
}
messenger.showSnackBar(
SnackBar(
content: Text(message),
),
);
}
Важно отметить, что, в отличие от других языков, использование блочных комментариев /* ... */
ограничено только блоками кода, которые мы хотим временно отключить, в остальных случаях всегда должны использоваться строчные комментарии либо //
, либо ///
.
Для того чтобы иметь возможность генерировать качественную документацию, руководство объясняет, какой должна быть общая структура блока комментариев. Он должен содержать одну строку в качестве краткого описания того, что делает код, за которой следует блок текста, где мы можем объяснить чуть более подробно то, что считаем нужным объяснить. Рекомендуется также привести пример кода, если мы считаем это необходимым, а также ссылки на другие компоненты проекта или внешние ресурсы, которые могут помочь лучше понять то, что мы анализируем.
/// An alternative semantics label for this text.
///
/// If present, the semantics of this widget will contain this
/// value instead of the actual text. This will overwrite any
/// of the semantics labels applied directly to the [TextSpan]s.
///
/// This is useful for replacing abbreviations or shorthands
/// with the full text value:
///
/// ```dart
/// Text(r'$$', semanticsLabel: 'Double dollars')
/// ```
final String? semanticsLabel;
Еще одно большое различие между Dart и другими языками заключается в том, что в других языках мы бы написали комментарий, подобный этому:
/**
* Parses the provided parameters and constructs a model accordingly.
*
* @param id: Id of the user
* @param name: Name of the user
* @param surname: Surname of the user
* @return A UserModel created from the provided params.
* @throws Exception An error if any parameter is empty.
*/
fun createModel(id: String, name: String, surname: String): UserModel {
// [...]
}
В Dart это будет выглядеть следующим образом:
/// Parses the given [id], [name] and [surname] to construct
/// and return a [UserModel] object.
///
/// Can throw an [Exception] if any parameter is empty.
UserModel createModel(String id, String name, String surname) {
// [...]
}
Как вы можете видеть, благоприятным является включение параметров и других соответствующих данных в само повествование комментария, что помогает комментарию быть короче и, возможно, более дидактичным.
В целом и с моей точки зрения, я думаю, что видение Dart о том, как писать комментарии, довольно элегантно и эффективно, и я приглашаю вас взглянуть на официальное руководство, чтобы понять все его особенности.
Однако одно дело — знать, как применять руководство, чтобы иметь возможность написать комментарий в том формате, который он должен иметь, а другое — понимать, когда комментарий необходим, а когда нет, поэтому теперь, когда мы ознакомились с основными аспектами синтаксиса, давайте посмотрим, как можно определить, какой код следует комментировать, а какой нет:
Когда комментировать
Общее правило, позволяющее понять, нужно ли добавлять комментарий, следующее:
Если бы коллега увидел этот код, смог бы он понять в общих чертах, для чего он нужен, просто увидев его описание, параметры или другую близлежащую информацию; менее чем за 5 секунд?
Если ответ положительный, то добавление комментария, вероятно, излишне. В противном случае я бы посоветовал вам либо добавить комментарий, либо рефакторить код, чтобы сделать его более понятным. На самом деле, я думаю, что последний вариант предпочтительнее, чем написание комментария, поскольку, на мой взгляд, код должен быть настолько коротким и лаконичным, насколько это возможно.
Давайте применим это увещевание на практике. Проверьте, сможете ли вы понять следующий блок кода менее чем за 5 секунд:
int sum(int first, int second) {
return first + second;
}
Я думаю, что, скорее всего, вам не понадобится и 2 секунд, чтобы понять его. Это очень наглядный пример того, когда подобный комментарий является лишним и совсем не нужным:
/// Returns the sum of [first] and [second].
int sum(int first, int second) {
return first + second;
}
Теперь посмотрите на следующий блок и попытайтесь понять его из кода:
Future<void> initialize({bool addAdditionalDelay = false}) async {
if (addAdditionalDelay) {
state = LauncherState.loading;
await Future.delayed(const Duration(milliseconds: _additionalDelay));
}
// Check legal acceptance.
final isLegalAccepted = await _preferencesRepo.isAcceptLegal();
if (!isLegalAccepted) {
state = LauncherState.acceptLegal;
return;
}
// Check internet access.
final connResult = await Connectivity().checkConnectivity();
if (connResult == ConnectivityResult.none) {
state = LauncherState.noConnection;
return;
}
// Get the API token.
String? token;
try {
token = await _sessionRepo.getToken();
_log.d('Obtained token: $token');
state = LauncherState.goToMain;
} catch (e) {
_log.e('Error while login: $e');
state = LauncherState.error;
}
}
Эта функция делает несколько вещей, и хотя я думаю, что вы сможете понять каждую часть, я также думаю, что человеку, вероятно, потребуется больше 5 секунд, чтобы понять ее. По этой причине, а также потому, что все является операцией инициализации, добавление краткого поясняющего комментария в начале может быть очень полезным:
/// Perform the initializations operations.
///
/// Optionally set [addAdditionalDelay] to true in order to simulate
/// a delay.
///
/// This can be useful if the user is clicking on the try again button,
/// so they know that something is happening.
Future<void> initialize({bool addAdditionalDelay = false}) async {
// [...]
}
Заключение
Дальше все зависит от опыта и здравого смысла. Если вы недавно начали программировать, не стоит слишком беспокоиться об этом, а если вы сомневаетесь, я рекомендую добавить комментарий. Я считаю, что такой подход лучше, чем ничего не делать и сводить с ума следующего парня, который пытается понять, что происходит в этом коде.
Немного хорошей практики, попытка расчленить код настолько, насколько это возможно, вполне достижима, чтобы быть в состоянии писать самообъясняющийся код в большинстве случаев. Однако для тех незначительных случаев или сложных алгоритмов, где нет возможности упростить код, лучше написать комментарий.