今回はオブジェクト指向設計で、elseは使用しないというテーマで書きます。
結論を先に言いますと、
elseは使用してはいけない。elseを使用したくなったら、そのクラスが仕事をしすぎていないか考える。
です。
else使用はクラスが仕事をしすぎている指標
ThoughtWorksアンソロジーのオブジェクト指向エクセサイズにて以下のように明記されています。
5.2.3 ルール2 else句を使用しないこと
ThoughtWorks アンソロジー
このルールに関しては、完全に同意します。elseを使用すると可読性が下がるのでやめたほうがよいです。
else文を使いたくなったら、そのクラスが仕事をしすぎている可能性があります。if-else で1つの仕事として成り立っています。その仕事を別クラスに委譲したほうがコードの可読性が上がります。
また、クラスを分割することで、よりクラスの役割が明確になるメリットもあります。
if-ifelse 文も同様に使用しすべきではないです。
elseを使用しない実装
では、elseを使用しない実装をやっていきます。ThoughtWorks アンソロジーと違った例です。
以下の例題で考えてみます。
x-y座標空間で、点の集合のラインを与えられたとき、以下のルールで補正した点の集合のラインを返すメソッドを作成する。
- 点が指定領域外の場合は、指定領域の一番近い点に移動する
- 連続して同じ点とならないようにする
elseを使用
1つの座標を表すクラスは↓
Cord.cs
namespace ElseNG
{
internal class Cord
{
public int x { get; }
public int y { get; }
public Cord(int x, int y)
{
this.x = x;
this.y = y;
}
public bool EqualCord(Cord other)
{
return x==other.x && y==other.y;
}
public override string ToString()
{
return "( " + x + ", " + y + ")";
}
}
}
点の集合のラインを補正して、補正したラインを出力するクラスLineRegionFactoryは↓
LineRegionFactory.cs
namespace ElseNG
{
/// <summary>
/// 領域内のラインファクトリー
/// </summary>
internal class LineRegionFactory
{
/// <summary>
/// 補正後の座標リストを生成する
/// </summary>
/// <param name="srcList">補正前の座標リスト</param>
/// <param name="xLeft">領域x左側</param>
/// <param name="xRight">領域x右側</param>
/// <param name="yDown">領域y下側</param>
/// <param name="yUp">領域y上側</param>
/// <returns>補正後の座標リスト</returns>
public List<Cord> Create(List<Cord> srcList, int xLeft, int xRight, int yDown, int yUp)
{
List<Cord> dstList = new List<Cord>();
Cord lastCord = new Cord(int.MaxValue, int.MaxValue);
foreach(Cord src in srcList)
{
// x座標の補正
int dstx;
if( src.x < xLeft)
{
dstx = xLeft;
}
else if( src.x > xRight)
{
dstx = xRight;
}
else
{
dstx = src.x;
}
// y座標の補正
int dsty;
if( src.y < yDown)
{
dsty = yDown;
}
else if( src.y > yUp)
{
dsty = yUp;
}
else
{
dsty = src.y;
}
Cord dst = new Cord(dstx, dsty);
// 座標が前回と一致しているなら追加しない
if( dst.EqualCord(lastCord))
{
continue;
}
dstList.Add(dst);
lastCord = dst;
}
return dstList;
}
}
}
x座標、y座標の補正の箇所でelseが出現しています。elseのことを何も考えていないとこのようなコードになるのではないでしょうか?
elseを未使用
では、先ほどのコードをelseを使用しない実装に修正します。
先ほどのコードは、x座標、y座標の補正をLineRegionFactoryで行っていました。この処理をCord で行ってみます。補正処理の仕事をCordクラスに委譲した形になります。
それでは、Cordクラスに補正した座標を返すメソッドを追加してみます。
Cord.cs
namespace ElseNG
{
internal class Cord
{
private int x;
private int y;
public Cord(int x, int y)
{
this.x = x;
this.y = y;
}
public bool EqualCord(Cord other)
{
return x==other.x && y==other.y;
}
/// <summary>
/// 領域内で補正した座標を返す
/// </summary>
/// <param name="xLeft"></param>
/// <param name="xRight"></param>
/// <param name="yDown"></param>
/// <param name="yUp"></param>
/// <returns></returns>
public Cord CorrectRegion(int xLeft, int xRight, int yDown, int yUp)
{
int dstX = Math.Max(xLeft, x);
dstX = Math.Min(xRight, dstX);
int dstY = Math.Max(yDown, y);
dstY = Math.Min(yUp, dstY);
return new Cord(dstX, dstY);
}
public override string ToString()
{
return "( " + x + ", " + y + ")";
}
}
}
CorrectRegionメソッドで座標の補正を行っています。このメソッドをCordクラスに追加しても特に違和感はありません。また、メンバのgetterを削除しました。
次は、LineRegionFactoryで追加したメソッドを使ってみます。
LineRegionFactory.cs
namespace ElseNG
{
/// <summary>
/// 領域内のラインファクトリー
/// </summary>
internal class LineRegionFactory
{
/// <summary>
/// 補正後の座標リストを生成する
/// </summary>
/// <param name="srcList">補正前の座標リスト</param>
/// <param name="xLeft">領域x左側</param>
/// <param name="xRight">領域x右側</param>
/// <param name="yDown">領域y下側</param>
/// <param name="yUp">領域y上側</param>
/// <returns>補正後の座標リスト</returns>
public List<Cord> Create(List<Cord> srcList, int xLeft, int xRight, int yDown, int yUp)
{
List<Cord> dstList = new List<Cord>();
Cord lastCord = new Cord(int.MaxValue, int.MaxValue);
foreach (Cord src in srcList)
{
// 座標の補正
Cord dst = src.CorrectRegion(xLeft, xRight, yDown, yUp);
// 座標が前回と一致しているなら追加しない
if (dst.EqualCord(lastCord))
{
continue;
}
dstList.Add(dst);
lastCord = dst;
}
return dstList;
}
}
}
else を使っていた個所を1行で書くことができました。
このように、座標の補正処理をCordクラスで実装することで、elseをなくすことができました。コードも可読性が上がったと思います。
まとめ
オブジェクト指向設計で、else文は使用しないというテーマで書きました。
elseは使用しないほうがいいです。else文を使用したくなったら、そのメソッドが仕事をしすぎていないか考えてみましょう。
コメント