はじめに
この内容は、”Clean Code for Dart”の内容を基に、和訳とメモや解説を個人用にまとめたものです。長いため、3回に分けて掲載しています。これは3回目の内容になります。
GitHub - williambarreiro/clean-code-dart: 📖 Clean Code concepts adapted for Dart
📖 Clean Code concepts adapted for Dart. Contribute to williambarreiro/clean-code-dart development by creating an account...
テスト
テストごとに単一の概念を持つ
// 悪い例
import 'package:test/test.dart';
test('String', () {
var string = 'foo,bar,baz';
expect(string.split(','), equals(['foo', 'bar', 'baz']));
string = ' foo ';
expect(string.trim(), equals('foo'));
});
// 良い例
// String グループ内でそれぞれのテストが単一の概念に焦点を当てている
// テストの意図がより明確になり、テストの失敗時に問題を特定しやすくなる
import 'package:test/test.dart';
group('String', () {
test('.split() splits the string on the delimiter', () {
const string = 'foo,bar,baz';
expect(string.split(','), equals(['foo', 'bar', 'baz']));
});
test('.trim() removes surrounding whitespace', () {
const string = ' foo ';
expect(string.trim(), equals('foo'));
});
});
Dart同時実行性
thenではなく、async/awaitを使用する
// 悪い例
final albumTitle = await client
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'))
.then((response) {
// ...
return title;
});
// 良い例
// async関数を使用して非同期処理を行っている
// awaitキーワードを使用することで、非同期処理が完了するまで処理を一時停止し、その結果を変数に代入
// async/awaitを使用する事で、読みやすく、ネストが深くならない
Future<String> getAlbumTitle() async {
final response = await client
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
// ...
return title;
}
Dartエラー処理
捕捉されたエラーを無視しない
// 悪い例
// エラーを単にコンソールに出力するだけでは、エラーが他の出力と混在して見落とされる可能性がある
// try/catchでコードを囲むということは、そこでエラーが発生する可能性があると思っているということであり、その場合の対処方法やコードパスを用意するべき
try {
functionThatMightThrow();
} catch (error) {
print(error);
}
// 良い例
// onキーワードを使用して特定の例外タイプをキャッチしている
try {
functionThatMightThrow();
} on Exception catch (e, s) {
// Option 1:
log('Error description...', error: e, stackTrace: s);
// Option 2:
notifyUserOfError(e, s);
// Option 3:
reportErrorToService(e, s);
}
DartFutureのエラーを無視しない
// 悪い例
// Futureやthenを使用する場合、エラーの処理を忘れないようにする
functionThatMightThrow().then((value) {
// ...
}).onError((e, s) {
print(e);
});
// 良い例
// エラーハンドラーでは、エラーが発生した際に行うべき対応を行う
functionThatMightThrow().then((value) {
// ...
}).onError((e, s) {
// Option 1:
log('Error description...', error: e, stackTrace: s);
// Option 2:
notifyUserOfError(e, s);
// Option 3:
reportErrorToService(e, s);
});
Dartフォーマット
正しい大文字を使用する
// 悪い例
const DAYS_IN_WEEK = 7;
const Bands = ['AC/DC', 'Led Zeppelin', 'The Beatles'];
void restore_database() {}
class animal {}
typedef predicate<T> = bool Function(T value);
// 良い例
// 一貫性のあるフォーマットを保持する
// 定数名にlowerCamelCaseを使用
const daysInWeek = 7;
const bands = ['AC/DC', 'Led Zeppelin', 'The Beatles'];
// 関数名にはlowerCamelCaseを使用
void restoreDatabase() {}
// クラス、列挙型、typedef、および型パラメータにはUpperCamelCaseを使用
class Animal {}
typedef Predicate<T> = bool Function(T value);
Dart関数呼び出し元と呼び出し先は近くに配置する
// 悪い例
// 関数呼び出し元と呼び出し先は近くに配置すべき
// 呼び出し元を呼び出し先の直上に配置するのが望ましい
class Smartphone {
// ...
String getOS() {
// ...
}
void showPlatform() {
final os = getOS();
final chipset = getChipset();
// ...
}
String getResolution() {
// ...
}
void showSpecifications() {
showPlatform();
showDisplay();
}
String getChipset() {
// ...
}
void showDisplay() {
final resolution = getResolution();
// ...
}
}
// 良い例
// showSpecifications() が showPlatform() と showDisplay() を呼び出しているので、showSpecifications() の近くに配置
// 呼び出し元と呼び出し先が近くにあると、コードが読みやすくなる
class Smartphone {
// ...
void showSpecifications() {
showPlatform();
showDisplay();
}
void showPlatform() {
final os = getOS();
final chipset = getChipset();
// ...
}
String getOS() {
// ...
}
String getChipset() {
// ...
}
void showDisplay() {
final resolution = getResolution();
// ...
}
String getResolution() {
// ...
}
}
Dartコメント
ビジネスロジックの複雑さがある部分にのみコメントを付ける
// 悪い例
// 良いコードは自己説明的であるべき
List<String> getCitiesNames(List<String> cities) {
// Cities names list
final citiesNames = <String>[];
// Loop through every city
for (final city in cities) {
// Gets only the string before the comma
final filteredCityName = city.split(',')[0];
// Add the filtered city name
citiesNames.add(filteredCityName);
}
// Returns the cities names list
return citiesNames;
}
// 良い例
// 不要なコメントを削除
// ビジネスロジックの複雑さを説明するためにのみ使用すべきであり、明らかなコードではコメントは不要
List<String> getCitiesNames(List<String> cities) {
final citiesNames = <String>[];
for (final city in cities) {
// Gets only the string before the comma
final filteredCityName = city.split(',')[0];
citiesNames.add(filteredCityName);
}
return citiesNames;
}
Dartコメントアウトされたコードを残さない
// 悪い例
doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();
// 良い例
// 古いコードはバージョン管理システムの履歴に残しておくべき
// コメントアウトされたコードを残すことは避ける
doStuff();
Dartジャーナルコメントを避ける
// 悪い例
// 履歴は git log で確認
/**
* 2016-12-20: Removidas monads, não entendia elas (RM)
* 2016-10-01: Melhoria utilizando monads especiais (JP)
* 2016-02-03: Removido checagem de tipos (LI)
* 2015-03-14: Adicionada checagem de tipos (JR)
*/
int combine(int a, int b) {
return a + b;
}
// 良い例
// 不要なコメントやジャーナルコメントはコードから削除
// バージョン管理システムを使ってコードの履歴を管理
int combine(int a, int b) {
return a + b;
}
Dartポジションマーカーを避ける
// 悪い例
// ポジションマーカーは通常、コードにノイズを加えるだけ
////////////////////////////////////////////////////////////////////////////////
// Programmer Instantiation
////////////////////////////////////////////////////////////////////////////////
final programmer = Programmer(
name: 'Jack',
linesOfCode: 500,
);
////////////////////////////////////////////////////////////////////////////////
// startProject implementation
////////////////////////////////////////////////////////////////////////////////
void startProject() {
// ...
};
// 良い例
// 適切な関数名や変数名、正しいインデントやフォーマットでコードに視覚的な構造を与える
final programmer = Programmer(
name: 'Jack',
linesOfCode: 500,
);
void startProject() {
// ...
};
Dartまとめ
内容を読んでいて、特にポリモーフィズムの扱いや不必要な文脈・コメントの追加、そしてポジションマーカーの適切な使用について、私自身気をつけないといけないなと感じました。”Clean Code for Dart”に書かれているポイントを意識することで、より読みやすく保守しやすいコードを作成する技術が身につくと思います。ただし、すべての場面でこれらの原則に厳密に従う必要はなく、柔軟に対応することも重要だと考えています。プログラミングの歴史は約70年程度とまだまだ浅いため、ベストプラクティスもどんどん更新されていくでしょう。