あなたはクリーン コードに関する私の本「コードを洗う」からの抜粋を読んでいます。 PDF、EPUB、ペーパーバック、Kindle 版として利用可能です。今すぐコピーを入手してください。
巧妙なコードは、おそらくこれまで見たことのない言語機能がどのように機能するかを知ることを期待される場合に、就職面接の質問や言語に関するクイズで目にすることがあります。これらすべての質問に対する私の答えは、「コードレビューに合格しない」です。
簡潔さと明瞭さを混同する人がいます。短いコード (簡潔さ) が常に最も明確なコード (明確さ) であるとは限りません。多くの場合、その逆です。コードを短くする努力は崇高な目標ですが、読みやすさを犠牲にしてはいけません。
同じアイデアをコードで表現する方法はたくさんあり、その中には他の方法より理解しやすいものもあります。私たちは常に、コードを読む次の開発者の認知的負荷を軽減することを目指す必要があります。すぐには分からないことにつまずくたびに、私たちは脳のリソースを無駄にしています。
情報: この章の名前は、Steve Krug の Web ユーザビリティに関する同名の書籍から「盗用」しました。
いくつかの例を見てみましょう。答えをカバーし、これらのコード スニペットが何を行うかを推測してみてください。次に、正しく推測した数を数えてください。
例 1:
const percent = 5; const percentString = percent.toString().concat('%');
このコードは数値に % 記号を追加するだけなので、次のように書き換える必要があります。
const percent = 5; const percentString = `${percent}%`; // → '5%'
例 2:
const url = 'index.html?id=5'; if (~url.indexOf('id')) { // Something fishy here… }
~ 記号は、ビット単位の NOT 演算子と呼ばれます。ここでの有益な効果は、indexOf() が -1 を返した場合にのみ偽の値を返すことです。このコードは次のように書き換える必要があります:
const url = 'index.html?id=5'; if (url.includes('id')) { // Something fishy here… }
例 3:
const value = ~~3.14;
ビットごとの NOT 演算子のもう 1 つのあいまいな使用法は、数値の小数部分を破棄することです。代わりに Math.floor() を使用してください:
const value = Math.floor(3.14); // → 3
例 4:
if (dogs.length + cats.length > 0) { // Something fishy here… }
これはすぐに理解できます。2 つの配列のいずれかに要素があるかどうかをチェックします。ただし、明確にしたほうがよいでしょう:
if (dogs.length > 0 && cats.length > 0) { // Something fishy here… }
例 5:
const header = 'filename="pizza.rar"'; const filename = header.split('filename=')[1].slice(1, -1);
これを理解するのに時間がかかりました。 filename="pizza" などの URL の一部があると想像してください。まず、文字列を = で分割し、2 番目の部分「pizza」を取得します。次に、最初と最後の文字をスライスしてピザを作成します。
ここではおそらく正規表現を使用します:
const header = 'filename="pizza.rar"'; const filename = header.match(/filename="(.*?)"/)[1]; // → 'pizza'
または、さらに良いのは、URLSearchParams API:
const header = 'filename="pizza.rar"'; const filename = new URLSearchParams(header) .get('filename') .replaceAll(/^"|"$/g, ''); // → 'pizza'
しかし、これらの引用は奇妙です。通常、URL パラメータを引用符で囲む必要はないため、バックエンド開発者に相談することをお勧めします。
例 6:
const percent = 5; const percentString = percent.toString().concat('%');
上記のコードでは、条件が true の場合にオブジェクトにプロパティを追加します。それ以外の場合は何も行いません。偽の値の構造化に依存するのではなく、構造化するオブジェクトを明示的に定義すると、その意図がより明白になります。
const percent = 5; const percentString = `${percent}%`; // → '5%'
私は通常、オブジェクトの形状が変わらないことを好むため、条件を値フィールド内に移動します。
const url = 'index.html?id=5'; if (~url.indexOf('id')) { // Something fishy here… }
例 7:
const url = 'index.html?id=5'; if (url.includes('id')) { // Something fishy here… }
この素晴らしいワンライナーは、0 から 9 までの数字で満たされた配列を作成します。 Array(10) は 10 個の 空 要素を含む配列を作成し、keys() メソッドはキー (0 からの数字) を返します。から 9) を反復子として使用し、スプレッド構文を使用して単純な配列に変換します。爆発する頭の絵文字…
for ループを使用して書き換えることができます。
const value = ~~3.14;
私はコード内のループを避けたいのですが、ループ バージョンの方が読みやすいです。
中間のどこかで Array.from() メソッドを使用します。
const value = Math.floor(3.14); // → 3
Array.from({length: 10}) は、10 個の 未定義 要素を含む配列を作成し、次に、map() メソッドを使用して、配列に 0 から 9 までの数値を入力します。
Array.from() のマップ コールバックを使用すると、より短く書くことができます。
if (dogs.length + cats.length > 0) { // Something fishy here… }
明示的な map() はもう少し読みやすく、Array.from() の 2 番目の引数が何をするのかを覚える必要はありません。さらに、Array.from({length: 10}) は Array(10) よりもわずかに読みやすくなっています。ほんの少しですが。
それで、あなたのスコアは何ですか?私の場合は3/7頃になると思います。
一部のパターンは、賢さと読みやすさの間の境界線を踏み越えます。
たとえば、ブール値を使用して偽の配列要素 (この例では null と 0) を除外します。
if (dogs.length > 0 && cats.length > 0) { // Something fishy here… }
私はこのパターンが許容できると思います。学習は必要ですが、代替案よりは優れています:
const header = 'filename="pizza.rar"'; const filename = header.split('filename=')[1].slice(1, -1);
ただし、両方のバリエーションで falsy 値が除外されるため、ゼロまたは空の文字列が重要な場合は、未定義または null を明示的にフィルターする必要があることに注意してください。
const header = 'filename="pizza.rar"'; const filename = header.match(/filename="(.*?)"/)[1]; // → 'pizza'
同じように見える 2 行のトリッキーなコードを見ると、それらは何らかの点で異なっていると思いますが、違いはまだわかりません。そうしないと、プログラマはコードをコピーペーストする代わりに、繰り返されるコードに対して変数または関数を作成する可能性があります。
たとえば、プロジェクトで使用する 2 つの異なるツール、Enzyme と Codeception のテスト ID を生成するコードがあります。
const header = 'filename="pizza.rar"'; const filename = new URLSearchParams(header) .get('filename') .replaceAll(/^"|"$/g, ''); // → 'pizza'
これら 2 行のコードの違いをすぐに見つけるのは困難です。 10 個の違いを見つけなければならなかった 2 枚の写真を覚えていますか?これが、このコードが読者に対して行うことです。
私は通常、コードの極端な DRY 化には懐疑的ですが、これは良いケースです。
情報: 「同じことを繰り返さない」原則については、「分割して征服するか、統合して緩和する」の章で詳しく説明します。
const percent = 5; const percentString = percent.toString().concat('%');
これで、両方のテスト ID のコードがまったく同じであることに疑いの余地はありません。
もっとトリッキーな例を見てみましょう。各テスト ツールに異なる命名規則を使用するとします。
const percent = 5; const percentString = `${percent}%`; // → '5%'
これら 2 行のコードの違いは気づきにくいため、名前の区切り文字 (- または _) だけがここでの違いであると確信することはできません。
このような要件を持つプロジェクトでは、このパターンが多くの場所で発生する可能性があります。これを改善する 1 つの方法は、各ツールのテスト ID を生成する関数を作成することです。
const url = 'index.html?id=5'; if (~url.indexOf('id')) { // Something fishy here… }
これはすでにかなり改善されていますが、まだ完璧ではありません。繰り返されるコードがまだ大きすぎます。これも修正しましょう:
const url = 'index.html?id=5'; if (url.includes('id')) { // Something fishy here… }
これは小さな関数を使用する極端な例であり、私は通常、これほどコードを分割することは避けるようにしています。ただし、この場合、特に新しい getTestIdProps() 関数を使用できる場所がプロジェクト内にすでにたくさんある場合には、うまく機能します。
ほぼ同じに見えるコードに微妙な違いがある場合があります。
const value = ~~3.14;
ここでの唯一の違いは、非常に長い名前を持つ関数に渡すパラメーターです。関数呼び出し内で条件を移動できます:
const value = Math.floor(3.14); // → 3
これにより、同様のコードが削除され、スニペット全体が短くなり、理解しやすくなります。
コードをわずかに変える条件に遭遇したときは常に、「この条件は本当に必要なのか?」と自問する必要があります。答えが「はい」の場合、私たちはもう一度自分自身に問いかける必要があります。多くの場合、特定の条件は実際必要ありません。たとえば、なぜさまざまなツールのテスト ID を個別に追加する必要があるのでしょうか?一方のツールがもう一方のテスト ID を使用するように設定することはできないでしょうか?十分に深く掘り下げてみると、誰も答えを知らないか、元の理由がもはや関連していないことがわかるかもしれません。
次の例を考えてみましょう:
if (dogs.length + cats.length > 0) { // Something fishy here… }
このコードは、assetsDir が存在しない場合と、assetsDir が配列ではない場合の 2 つのエッジケースを処理します。また、オブジェクト生成コードも重複しています。 (ネストされた 3 項の話はやめましょう…) 重複と少なくとも 1 つの条件を取り除くことができます。
if (dogs.length > 0 && cats.length > 0) { // Something fishy here… }
Lodash の CastArray() メソッドが配列内で未定義をラップするのが好きではありません。これは私が期待していたものではありませんが、それでも結果はより単純です。
CSS には省略表現のプロパティがあり、開発者はそれらを過剰に使用することがよくあります。単一のプロパティで同時に複数のプロパティを定義できるという考え方です。良い例を次に示します:
const header = 'filename="pizza.rar"'; const filename = header.split('filename=')[1].slice(1, -1);
これは次と同じです:
const header = 'filename="pizza.rar"'; const filename = header.match(/filename="(.*?)"/)[1]; // → 'pizza'
コードは 4 行ではなく 1 行ですが、何が起こっているかは明らかです。要素の 4 辺すべてに同じマージンを設定しています。
次に、この例を見てください:
const percent = 5; const percentString = percent.toString().concat('%');
彼らが何をするのかを理解するには、次のことを知る必要があります。
これにより、不必要な認知的負荷が生じ、コードの読み取り、編集、レビューが困難になります。私はそのような略記を避けます。
短縮プロパティに関するもう 1 つの問題は、変更するつもりのないプロパティに値が設定される可能性があることです。次の例を考えてみましょう:
const percent = 5; const percentString = `${percent}%`; // → '5%'
この宣言は、Helvetica フォント ファミリ、フォント サイズ 2rem を設定し、テキストを斜体および太字にします。ここでは表示されませんが、行の高さもデフォルト値の標準値に変更されるということです。
私の経験則では、単一の値を設定する場合にのみ短縮プロパティを使用します。それ以外の場合は、手書きプロパティを好みます。
ここにいくつかの良い例を示します:
const url = 'index.html?id=5'; if (~url.indexOf('id')) { // Something fishy here… }
避けるべき例をいくつか示します:
const url = 'index.html?id=5'; if (url.includes('id')) { // Something fishy here… }
短縮プロパティを使用すると確かにコードは短くなりますが、多くの場合、著しく読みにくくなるため、使用には注意してください。
条件を取り除くことが常に可能であるとは限りません。ただし、コード分岐の違いを見つけやすくする方法があります。私のお気に入りのアプローチの 1 つは、並列コーディング と呼ばれるものです。
次の例を考えてみましょう:
const value = ~~3.14;
個人的な不満かもしれませんが、戻り値のステートメントが異なるレベルにあり、比較が難しくなるのが嫌いです。これを修正するために else ステートメントを追加しましょう:
const value = Math.floor(3.14); // → 3
これで、両方の戻り値が同じインデント レベルになり、比較しやすくなりました。このパターンは、どの条件分岐もエラーを処理していない場合に機能します。この場合、早期にリターンする方が良いアプローチとなります。
情報: 早期返品については「回避条件」の章で説明します。
これは別の例です:
if (dogs.length + cats.length > 0) { // Something fishy here… }
この例では、ブラウザーでリンクのように動作し、アプリで確認モーダルを表示するボタンがあります。 onPress プロパティの条件が逆転しているため、このロジックがわかりにくくなっています。
両方の条件を肯定的にしましょう:
if (dogs.length > 0 && cats.length > 0) { // Something fishy here… }
これで、プラットフォームに応じて onPress を設定するか、props をリンクするかのいずれかを設定することは明らかです。
コンポーネント内の Platform.OS === 'web' 条件の数、または条件付きで設定する必要がある props の数に応じて、ここで停止することも、さらに一歩進めることもできます
条件プロパティを別の変数に抽出できます:
const header = 'filename="pizza.rar"'; const filename = header.split('filename=')[1].slice(1, -1);
次に、条件全体を毎回ハードコーディングする代わりに、これを使用します。
const header = 'filename="pizza.rar"'; const filename = header.match(/filename="(.*?)"/)[1]; // → 'pizza'
いずれにしてもアプリでは使用されないため、ターゲット prop も Web ブランチに移動しました。
私が 20 代の頃、物事を覚えるのはそれほど問題ではありませんでした。読んだ本や取り組んでいたプロジェクトのすべての機能を思い出すことができました。 40代になった今はそんなことはありません。私は今、トリックを一切使用していないシンプルなコードを重視しています。私は、検索エンジン、ドキュメントへの素早いアクセス、すべてを頭の中に留めずにコードについて推論し、プロジェクトをナビゲートするのに役立つツールを重視しています。
私たちは現在の自分のためではなく、数年後の自分のためにコードを書くべきです。考えることは難しく、たとえ難しいコードや不明瞭なコードを解読する必要がなくても、プログラミングには多くのことが必要です。
以下について考え始めます:
フィードバックがある場合は、マストドンで私にツイートするか、GitHub で問題をオープンするか、artem@sapegin.ru にメールしてください。コピーを入手してください。
以上がコードを洗う: 考えさせないでくださいの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。