在昨天的例子中,我們把x, y抽到Point類別,讓這段Kata代碼擺脫資料泥團的壞味道。雖然解決了資料泥團的問題,但是這段代碼還是有其他的壞味道存在。
public class Dartboard
{
private readonly Dictionary<DistanceRange, string> _magniDistanceDict = ...
private readonly Dictionary<AngleRange, int> _scoreAngleDict = ...
private readonly List<string> _fixResultMagni = ...
public string GetScore(Point point)
{
var mangi = GetMagniByPosition(point);
var score = _fixResultMagni.Contains(mangi) ? "" : GetScoreByPosition(point);
return mangi + score;
}
private string GetMagniByPosition(Point point)
{
var distance = point.GetDistance();
return _magniDistanceDict.FirstOrDefault(entry => entry.Key.IsThisDistance(distance)).Value;
}
private string GetScoreByPosition(Point point)
{
var angle = point.GetAngle();
return Convert.ToString(_scoreAngleDict.First(entry => entry.Key.IsThisRange(angle)).Value);
}
private double GetDistance(Point point)
{
return Math.Sqrt(point.X * point.X + point.Y * point.Y);
}
private double GetAngle(Point point)
{
var result = Math.Atan2(point.Y, point.X) * 180.0 / Math.PI;
return result > 0 ? result : 360 + result;
}
}
昨天的代碼中有兩個方法GetDistance和GetAngle被省略沒有列出來,分別是根據點的位置計算距離與角度。如果仔細觀察這兩個方法可以發現,這兩個方法只都使用到了Point類別,不像其他方法使用到了Dartboard的欄位。
在上面的例子中Dartboard直接使Point的屬性去計算距離和角度,這是另外一個壞味道:Feature Envy。因為Dartboard需要使用Point的X, Y去計算距離和角度,導致Dartboard相依於Point的X, Y,也讓Point需要暴露X, Y給Dartboard使用。
我們可以把Point移進GetDistance和GetPoint移進Point類別中,讓Point透過自己的方法去計算距離角度。這樣也能把x, y更好的封裝在Point類別裡面,隱藏實作細節。
public class Point
{
private readonly double _x;
private readonly double _y;
...
public double GetDistance()
{
return Math.Sqrt(_x * _x + _y * _y);
}
public double GetAngle()
{
var result = Math.Atan2(_y, _x) * 180.0 / Math.PI;
return result > 0 ? result : 360 + result;
}
}
很多時候在比較不熟悉Domain的情況下,我們很難在一開始設計的時候就物件應該如何設計,應該要有什麼類別,或者是什麼方法應該在什麼類別裡面。但是只要透過不斷的察覺壞味道,從代碼的相依來看出類別之間的關係並且即時重構,就能讓我們的代碼越來越乾淨。