打造可維護軟體C#版 第五章 讓程式碼單元的介面保持簡單
第五章談到程式碼函式參數數量的化簡,主要有兩種重構方法「參數物件」和「使用方法物件替代方法」。
指導方針:
限制每個程式碼單元的參數不能超過4個。
透過將多個參數提取到物件來達成這件事。
較少的參數讓程式碼單元更容易理解和重新利用。
範例示範引進參數物件(Introduce Parameter Object)的重構方法
惡例Render方法有6個參數。
private void Render(Square square, Graphics g, int x, int y, int w, int h)
{
square.Sprite.Draw(g, x, y, w, h);
foreach (Unit unit in square.Occupants)
{
unit.Sprite.Draw(g, x, y, w, h);
}
}
可建立Rectangle類別,其中有Point 、Width 和 Height。
public class Rectangle
{
public Point Position { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public Rectangle(Point position, int width, int height)
{
this.Position = position;
this.Width = width;
this.Height = height;
}
}
使用參數物件之後,原本Render方法縮減到3個參數。
private void Render(Square square, Graphics g, Rectangle r)
{
Point position = r.Position;
square.Sprite.Draw(g, position.X, position.Y, r.Width, r.Height);
foreach (Unit unit in square.Occupants)
{
unit.Sprite.Draw(g, position.X, position.Y, r.Width, r.Height);
}
}
可再繼續化簡
private void Render(Square square, Graphics g, Rectangle r)
{
Point position = r.Position;
square.Sprite.Draw(g, r);
foreach (Unit unit in square.Occupants)
{
unit.Sprite.Draw(g, r);
}
}
5.1 動機
簡短介面更容易理解及重利用
具有簡短介面的方法比較容易修改
5.2 如何使用本指導方針?
冗長介面通常不是主要的問題,而是一種程式碼壞味道,意味著更深層的可維護性問題。
惡例顯示有冗長參數介面的方法
public void DoBuildAndSendMail(MailMan m, string firstName, string lastName,
string division, string subject, MailFont font, string message1,
string message2, string message3)
{
// Format the email address
string mId = $"{firstName[0]}.{lastName.Substring(0, 7)}" +
$"@{division.Substring(0, 5)}.compa.ny";
// Format the message given the content type and raw message
MailMessage mMessage = FormatMessage(font,
message1 + message2 + message3);
// Send message
m.Send(mId, subject, mMessage);
}
將參數分類之後改寫成物件參數,成功地減少參數數量
public void DoBuildAndSendMail(MailMan m, MailAddress mAddress,
MailBody mBody)
{
// Build the mail
Mail mail = new Mail(mAddress, mBody);
// Send the mail
m.SendMail(mail);
}
public class Mail
{
public MailAddress Address { get; set; }
public MailBody Body { get; set; }
public Mail(MailAddress mAddress, MailBody mBody)
{
this.Address = mAddress;
this.Body = mBody;
}
}
public class MailBody
{
public string Subject { get; set; }
public MailMessage Message { get; set; }
public MailBody(string subject, MailMessage message)
{
this.Subject = subject;
this.Message = message;
}
}
public class MailAddress
{
public string MsgId { get; private set; }
public MailAddress(string firstName, string lastName,
string division)
{
this.MsgId = $"{firstName[0]}.{lastName.Substring(0, 7)}" +
$"@{division.Substring(0, 5)}.compa.ny";
}
}
如果方法的各個參數無法妥適地組織在一起,該怎麼辦?
public static void DrawBarChart(Graphics g,
CategoryItemRendererState state,
Rectangle graphArea,
CategoryPlot plot,
CategoryAxis domainAxis,
ValueAxis rangeAxis,
CategoryDataset dataset)
{
// ..
}
第一種方法「多載」
public static void DrawBarChart(Graphics g, CategoryDataset dataset)
{
Charts.DrawBarChart(g,
CategoryItemRendererState.DEFAULT,
new Rectangle(new Point(0, 0), 100, 100),
CategoryPlot.DEFAULT,
CategoryAxis.DEFAULT,
ValueAxis.DEFAULT,
dataset);
}
第二種方法「使用方法物件替代方法」
public class BarChart
{
private CategoryItemRendererState state = CategoryItemRendererState.DEFAULT;
private Rectangle graphArea = new Rectangle(new Point(0, 0), 100, 100);
private CategoryPlot plot = CategoryPlot.DEFAULT;
private CategoryAxis domainAxis = CategoryAxis.DEFAULT;
private ValueAxis rangeAxis = ValueAxis.DEFAULT;
private CategoryDataset dataset = CategoryDataset.DEFAULT;
public BarChart Draw(Graphics g)
{
// ..
return this;
}
public ValueAxis GetRangeAxis()
{
return rangeAxis;
}
public BarChart SetRangeAxis(ValueAxis rangeAxis)
{
this.rangeAxis = rangeAxis;
return this;
}
// More getters and setters.
}
private void ShowMyBarChart()
{
Graphics g = this.CreateGraphics();
BarChart b = new BarChart()
.SetRangeAxis(myValueAxis)
.SetDataset(myDataset)
.Draw(g);
}
《Clean Code》有提到火車寫法不好,用這種寫法要考慮。
5.3 常見的反對意見
反對意見:參數物件具有龐大的介面
應該思考引進物件之結構,以及與其他程式碼之間的關係。
反對意見:重構冗長介面並未改善我的情況
避免冗長的介面並不是只依靠重構,可持續將不同職責拆分到各個方法。
反對意見:框架或程式庫已經定義了具有冗長參數列的介面
部分框架或程式庫有冗長參數介面,可用包裹或適配器的方式隔離框架或程式庫。
5.4 參考
留言
張貼留言