1 つのメソッドは 1 つのことに専念する
メソッドはできるだけ単純にしよう。読んで解読する人のことを考えよう。
ここでは例として、指定の文字列中の指定の単語を "<B>" と "</B>" で囲んで強調表示に設定する場合を考える。この動作を分解すると、
- 指定の文字列から単語を抽出する。
- 抽出した単語が指定のものならば強調表示する。
という 2 つの内容に分けられる。これを 1 つのメソッドで実現してみると、
using System.Collections.Generic; namespace WordReading { public class ReadMethods { /// <summary> /// 指定の文字列中の指定の単語を強調表示に設定する。一つのメソッドで実装する場合。 /// </summary> public static string ReadAndEmphasizeInOneMethod( string line, List<string> wordsToEmphasize) { string result = string.Empty; for (int index = 0; index < line.Length; /* index は場合に応じて増やす */) { // 現在位置の文字を取得し、抽出した単語に設定し、次の文字に移動する。 char firstChar = line[index]; string extractedWord = firstChar.ToString(); ++index; if (IsAlpha(firstChar)) { // アルファベットなら、続くアルファベットを抽出する。 for ( ; index < line.Length; ++index) { char nextChar = line[index]; if (!IsAlpha(nextChar)) { break; } extractedWord += nextChar; } } // 抽出した単語を強調する場合は "<B>" と "</B>" で囲む。強調しない場合はそのまま。 if (wordsToEmphasize.Contains(extractedWord)) { result += "<B>" + extractedWord + "</B>"; } else { result += extractedWord; } } return result; } public static bool IsAlpha(char ch) { return char.IsUpper(ch) || char.IsLower(ch); } } }
ということになる。ループや if 文の構成も複雑だが、文字列の現在位置を保持する変数 index を 2 つの for ループで使った上、
いろいろなところで参照・変更しており、それが解読をより難しくしている。
この動作を、
- 指定の文字列から単語を抽出するクラス WordReader
- WordReader が抽出した単語が指定のものならば強調表示するメソッド
の 2 つに分けてみる。WordReader クラスは、以下のようになる。
using System; namespace WordReading { public class WordReader { private readonly string m_line; private int m_index; private string m_word; public WordReader(string line) { m_line = line; m_index = 0; m_word = string.Empty; } /// <summary> /// ReadNextWord() で読み込んだ単語の文字列を取得します。 /// </summary> public string Word { get { return m_word; } } /// <summary> /// 次の単語を読み込みます。読み込む単語は、アルファベットの並び、あるいは、 /// アルファベット以外の場合はその 1 文字です。読み込んだ単語は Word プロパティで取得します。 /// </summary> /// <returns> /// 単語を読み込んだ場合は true を、 /// 文字列の最後まで到達しており単語が読み込めなかった場合は false を返します。 /// </returns> public bool ReadNextWord() { m_word = string.Empty; if (EndOfLine) { return false; } else { char firstChar = CurrentChar; AddCurrentChar(); if (ReadMethods.IsAlpha(firstChar)) { // 最初の文字がアルファベットならば、それに続くアルファベットの並びを読み込む。 while (!EndOfLine && ReadMethods.IsAlpha(CurrentChar)) { AddCurrentChar(); } } return true; } } private bool EndOfLine { get { return m_line.Length <= m_index; } } private char CurrentChar { get { if (EndOfLine) { throw new InvalidOperationException("EndOfLine のとき CurrentChar は取得できません。"); } return m_line[m_index]; } } private void AddCurrentChar() { if (EndOfLine) { throw new InvalidOperationException("EndOfLine のとき AddCurrentChar() は呼び出せません。"); } m_word += CurrentChar; ++m_index; } public override string ToString() { string str = string.Format("Token=\"{0}\"", Word); return str; } } }
WordReader が抽出した単語が指定のものならば強調表示するメソッド ReadAndEmphasizeByUsingWordReader は、次のようになる。
/// <summary> /// 指定の文字列中の指定の単語を強調表示に設定する。 /// "単語の抽出" と "指定の単語の強調表示" を分離する場合。 /// </summary> public static string ReadAndEmphasizeByUsingWordReader(string line, List<string> wordsToEmphasize) { string result = string.Empty; WordReader reader = new WordReader(line); // 単語が読める間ループ while (reader.ReadNextWord()) { if (!wordsToEmphasize.Contains(reader.Word)) { // 読み込んだ単語が強調するものではない場合、そのまま結果に追加する。 result += reader.Word; } else { // 強調する単語の場合は "<B>" と "</B>" で囲んで結果に追加する。。 result += "<B>" + reader.Word + "</B>"; } } return result; }
1 つのメソッドは 1 つのことに専念し、他のメソッドやクラスに任せられることを分離すると、
プログラムは単純になり、より理解しやすくなる。