ryubloblog’s diary

宮城県の仙台で働くプログラマーです。

読みやすいコードを書くために気をつけていること

業務でコードレビューを受けたり、コードレビューをしたりする機会が増えてきたので、
個人的に読みやすく綺麗なコードを書くために気をつけていることをまとめたいと思います。
また、最近はTypeScript/React.jsでフロントエンド開発がメインのため、
サンプルコードはそちらを使用したいと思います。

読みやすいコード/綺麗なコードとは

読みやすいコード、綺麗なコードの解釈には個人差があると思いますが、
ざっくり下記項目ができていたらオッケーとします。

  • 変数名、関数名が適切な名前になっていること
  • プロジェクトの命名規則に統一されていること
  • 改行が適切に行われていること
  • ネストが深すぎないこと
  • 不要なコメントがないこと/コメントが適切に残されていること
  • 処理の共通化が行われていること

では、1つずつまとめていきます。

変数名、関数名が適切な名前になっていること

まずは、変数名、関数名が適切な名前になっていることについてですが、
変数名と関数名の2つに分けてまとめていきたいと思います。

変数名

booleanの変数名
例:サイドバーを開くか開かないかの条件判定

const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(true);

booleanを判定する命名としてはis~~と記述してあげることで、
その変数名はtrueなのかfalseなのか直感的にわかるようになります。
また表示するかしないかの判定にはshow~~命名することで、
直感的にわかりやすくなりおすすめです。

関数名

フロントの開発では特になのですが、入力フォームのonChangeonBlur等の
イベントハンドラーを実装することがあるかと思います。
各イベントのハンドラーが直感的にわかる関数名として、
下記サンプルになります。

  const onChangeInputHandler = useCallback((e: ChangeEvent<HTMLInputElement>): void => {
    // change処理
  }, []);
  const onBlurInputHanlder = useCallback((e: FocusEvent<HTMLInputElement>): void => {
    // blur処理
  }, []);
  const onSubmitFormHandler = useCallback((e: FormEvent<HTMLFormElement> | undefined) => {
    // submit処理
  }, []);

上記のサンプルコードの各ハンドラーではどのような処理が行われるか関数名を見ただけで、
イメージができるようになっていると思います。(なっていなかったらすみません。。。)

変数名/関数名まとめ

変数名と関数名に共通して言えることは、パッと見ただけでどのような値を持っている変数なのか、
どのような関数で、どのような処理を行っているのかが、ソースを1行1行目を凝らして見なくても
わかる命名になっていることが重要ということです。

プロジェクトの命名規則に統一されていること

これは言うまでもないとは思うのですが、変数名や関数、ファイル名の命名がプロジェクトで
統一されていることが一番大切です。
開発者それぞれが、バラバラの命名を行っている場合には、コードレビューをするコストも上がりますし、
何よりメンテナンスする際に、処理を追うのが大変になります。
なので、プロジェクトで決められた命名規則に沿って開発を行うことが、今後の開発やメンテナンスの
コストを下げることができるので、とても重要です。
ただし、現在の命名規則より良いものがある場合等は、プロジェクトのリーダー等に提案するという
行動は積極的に行っていくべきだと考えています。

改行が適切に行われていること

例としては下記の3点になります。

  • 1行が長すぎる問題
  • HTML要素の改行問題

具体的に説明をしていきたいと思います。

1行が長すぎる問題

これはコードレビューをするまたはしてもらう際の問題点になります。

上記ピクチャの48行目に着目してください。
ここでは、1行の記述が長いため自動的に改行がされてしまっています。
この自動的な改行があるとソースコードを読むのが一気に大変になります。
またエディターで2分割して開いた場合に横スクロールをしないと
ソースの全体が見えないようになってしまいます。
なので、下記ピクチャのように適宜改行を入れることが重要です。

HTML要素の改行問題

この問題は好みが分かれるかもしれないのですが、inputbutton等の
要素に適宜、改行を入れないととてもソースコードが汚く感じます。
例:改行がバラバラ

      <TextField name="password" type="password" fullWidth label="パスワード" margin="normal"
        variant="outlined" onBlur={onBlurInputHanlder} onChange={onChangeInputHandler}
        value={values.password} error={Boolean(touched.password && errors.password)}
        helperText={touched.password && errors.password} />

例:改行の統一

      <TextField
        name="password"
        type="password"
        fullWidth
        label="パスワード"
        margin="normal"
        variant="outlined"
        onBlur={onBlurInputHanlder}
        onChange={onChangeInputHandler}
        value={values.password}
        error={Boolean(touched.password && errors.password)}
        helperText={touched.password && errors.password}
      />

上記の例は大袈裟かもしれないですが、明らかに改行が統一されている方が
綺麗にかつ読みやすいコードになっていると思います。

改行が適切に行われていることまとめ

改行問題で紹介したことは、フロントエンドであればESLintの導入を行えば防げるので、
フロントエンド開発を行う上ではESLintの導入は必須であると思います。

次回

少々、記事が長くなってしまったので、今回説明しきれなかった下記は次回にしたいと思います。

  • ネストが深すぎないこと
  • 不要なコメントがないこと/コメントが適切に残されていること
  • 処理の共通化が行われていること

再帰関数について【PHP】【Python】

前回の記事で少し触れた再帰関数についてまとめていきたいと思います。

再帰関数とは

自分自身を呼び出す関数のことを再帰関数と言います。
自分自身を呼び出すとはどういうことなのか感じる方もいると思いますが、ソースを読んだり、
ソースを書いてみるとなんとなくイメージができると思うので実際にやっていきましょう。

例題1:階乗の計算

標準入力

 N

入力Nの階乗を求めます。
再帰関数を使用しない場合は下記のようになります。

処理手順

  • for文を使用し、$i = 1がNになるまで掛け続ける

実装例

<?php

$n = trim(fgets(STDIN));

$answer = 1;
for ($i = 1; $i <= $n; $i++) {
  $answer *= $i;
}

echo $answer;
n = int(input())

answer = 1
for i in range(1, n + 1):
  answer *= i

print(answer)

では再帰関数を使用する場合を考えます。
再帰関数では関数を定義するため、条件を洗い出します。
下記が処理条件になります。

処理条件

  • Nが1の場合には1を返す
  • Nが2以上の場合には(N - 1の計算結果) * Nを返す

処理イメージ

※ 関数名をfactorial()とし、N = 5とします。

  • factorial(5)を呼ぶ
  • factorial(4)が呼ばれる
  • factorial(3)が呼ばれる
  • factorial(2)が呼ばれる
  • factorial(1)が呼ばれ、Nが1なので1を返す
  • factorial(2)factorial(1)で返ってきた1と2を掛け、計算結果を返す
  • factorial(3)factorial(2)で返ってきた2と3を掛け、計算結果を返す
  • factorial(4)factorial(3)で返ってきた6と4を掛け、計算結果を返す
  • factorial(5)factorial(4)で返ってきた24と5を掛け、計算結果を返す

実装は下記のようになります。

実装例

<?php

function factorial($n)
{
  if ($n === 1) return 1;
  return factorial($n - 1) * $n;
}

$n = trim(fgets(STDIN));
echo factorial($n);
def factorial(n):
  if n == 1:
    return 1

  return factorial(n - 1) * n

n = int(input())
print(factorial(n))

補足

ここで再帰関数を実装する際の注意点として「ベースケース」というものを定義する必要があります。
上記の再帰関数では

  • PHPではif ($n === 1) return 1;の箇所
  • Pythonではif n == 1:の箇所

この場合分けを定義しない場合には処理が終わらなくなってしまうので注意が必要です。

例題2:フィボナッチ数列

ここで一旦、漸化式についてまとめたいと思います。
漸化式というのは、数列において前項の計算結果からその項の値を求める規則のことです。
簡単に説明すると前の計算結果を用いて新しい計算結果を求める的な感じです。
例題1の階乗の例でいうところの処理条件のところになります。

ではフィボナッチ数列についてやっていきたいと思いますが下記が漸化式になります。

  • a1 = 1, a2 = 1
  • an - 1 + an - 2

つまり、最初の項と2項目までは1でそれ以降は、前の2つの項を足した値になるということになります。

実装例

標準入力

N

Nのフィボナッチ数列を求めます。

<?php

function fibonacci($n)
{
  if ($n <= 2) return 1;
  return fibonacci($n - 1) + fibonacci($n - 2);
}

$n = trim(fgets(STDIN));
print(fibonacci($n));
def fibonacci(n):
  if n <= 2:
    return 1

  return fibonacci(n - 1) + fibonacci(n - 2)

n = int(input())
print(fibonacci(n))

最後に

再帰関数は、処理イメージを持つのがやや複雑な部分がありますが、
地道に処理を追っていけば理解できるものだと思っています。
また、漸化式を応用した動的計画法についてもまとめていけたらと考えています。

最大公約数を求めるプログラムを様々な方法で解く【PHP】【Python】

現在進行中でアルゴリズムの学習をしていて、最大公約数を求めるプログラムを書く際に、
様々なコードの書き方を学んだので記事にしたいと思います。
下記の形で入力されます。

A B

A Bの条件としてA < Bとしています。(条件分岐を付け加えれば良い話なのですが、、)

方法1:共通に割れる数を1から求める方法

この方法は2つの数を1から順に割っていき、割り切れた最大値を求めるプログラムになります。

下記、PHPでの実装例になります。

<?php

[$a, $b] = explode(' ', trim(fgets(STDIN)));

function gcd($a, $b)
{
  $answer = 0;
  for ($i = 1; $i <= min($a, $b); $i++) {
    if ($a % $i === 0 && $b % $i === 0) $answer = $i;
  }
  return $answer;
}

echo gcd($a, $b);

この方法では、余りの計算を行う際に2×min($a, $b)回行うことになるので、効率的な方法ではないと言えます。

方法2:ユークリッドの互除法を使用する

この方法はユークリッドの互除法というアルゴリズムを用いて、最大公約数を求めます。

ユークリッドの互除法の処理手順は
1. A,Bの大きい方の数を「大きい方の数を小さい方の数で割った余り」に置き換える
2. 1を繰り返し、どちらかが0になったら処理を止める
3. 0ではない方の数が最大公約数の値

下記、PHPPythonでの実装例になります。

<?php

[$a, $b] = explode(' ', trim(fgets(STDIN)));

function gcd($a, $b)
{
  while ($a >= 1 && $b >= 1) {
    if ($a < $b) {
      $b = $b % $a;
    } else {
      $a = $a % $b;
    }
  }
  if ($a >= 1) return $a;
  return $b;
}

echo gcd($a, $b);

a, b = map(int, input().split())

def gcd(a, b):
  while a >= 1 and b >= 1:
    if a < b:
      b = b % a
    else:
      a = a % b
  if a >= 1:
    return a
  return b

print(gcd(a, b))

この方法の場合、最大公約数を求める時の計算量はO(log(A+B))となるらしいので、
方法1に比べかなり効率的になりました。
※ 計算量については勉強中なので、詳しくはのちのブログでまとめたいと思います。

方法3:再帰関数を使用する

この方法は方法2の処理を簡潔に記述することができるものになるので、新しい方法ではなく、
コードの記述量を減らすことができるテクニックのようなものになります。

下記、PHPPythonでの実装例になります。

<?php

[$a, $b] = explode(' ', trim(fgets(STDIN)));

function gcd($a, $b)
{
  if ($b === 0) return $a;
  return gcd($b, $a % $b);
}

echo gcd($a, $b);
a, b = map(int, input().split())

def gcd(a, b):
  if b == 0:
    return a
  return gcd(b, a % b)

print(gcd(a, b))

再帰関数を使用することによってコードの記述量を大幅に減らすことができました。
ただ、再帰関数は処理イメージを掴みにくい点があるので、
再帰関数の処理イメージについては別で詳しくまとめれたらと思います。

最後に

今回が初の投稿となるのですが、ここまで読んでいただきありがとうございました。
まだまだ、不慣れな部分はありますがよろしくお願いいたします。