一个函数应该写多长?
最近在看公司源代码的时候,经常有一些超长函数出现,甚至超过1000多行的函数都大有存在,这大大影响我对代码的理解,所以写下此文,当然只是自己的想法,不强加于人,只为勉励自己。
在以往的软件开发中,对于函数我也是想写多长就写多长,从来不去想它有多长这个“无聊的问题”,因为对于一个函数应该写多长并没有一个确切的定义,这应该看具体情况决定。
我个人觉得,无论是类还是函数,都应该满足单一职责原则,如果出现一个函数过长或者代码块嵌套过深的情况,常常是因为没有满足这一原则,这两种情况都能够通过更好的重构来解决。
以我工作中的一个代码片段为例来看一下,函数写多长,如何拆分函数。
1/**//// <summary>
2/// 时实窗体类
3/// </summary>
4public partial class iRealTimeInfo : Form
5{
6 ArrayList arrRealTime = new ArrayList();
7
8 public iRealTimeInfo()
9 {
10 InitializeComponent();
11 }
12
13 /**//// <summary>
14 /// 加载窗体事件
15 /// </summary>
16 private void iRealTimeInfo_Load(object sender, EventArgs e)
17 {
18 从XML文件中读取并获得所有曲线实时模板类型#region 从XML文件中读取并获得所有曲线实时模板类型
19 string strXmlFile = Application.StartupPath + @"\Templete\SystemTemplate";
20 XmlDocument doc = new XmlDocument();
21 doc.Load(strXmlFile + @"\SystemTemplate.xml");
22 foreach (XmlNode n in doc.ChildNodes)
23 {
24 string sName = n.Name;
25 if (sName == "Info")
26 {
27 foreach (XmlNode n2 in n.ChildNodes)
28 {
29 sName = n2.Name.ToLower();
30 switch (sName)
31 {
32 case "realtime": //找到实时模板根结点
33 foreach (XmlNode n3 in n2.ChildNodes)
34 {
35 string sXmlName = n3.InnerText;
36 string sXmlTitleCn = n3.Attributes["name"].Value;
37 string sXmlTitleEn = n3.Attributes["nameEn"].Value;
38 string sXmlAxisType = n3.Attributes["type"].Value;
39 string sXmlChartModel = n3.Attributes["chartmodel"].Value;
40 string sXmlDataType = n3.Attributes["datatype"].Value;
41
42 TemplateList TL = new TemplateList();
43 TL.TemplateActc = "000000";
44 TL.TemplateName = sXmlName.Split('.')[0];
45 TL.TemplateTitleCn = sXmlTitleCn;
46 TL.TemplateTitleEn = sXmlTitleEn;
47 TL.TemplateAxisType = sXmlAxisType;
48 TL.TemplateType = "0";
49 TL.RealTimeHistory = "1";
50 TL.ChartModel = sXmlChartModel;
51 TL.DataType = sXmlDataType;
52
53 验证模板文件是否存在#region 验证模板文件是否存在
54 String sFilePath = strXmlFile + @"\" + sXmlName;
55 if (System.IO.File.Exists(sFilePath))
56 {
57 arrRealTime.Add(TL);
58 }
59 #endregion
60 }
61 break;
62 }
63 }
64 }
65 }
66 #endregion
67
68 加载数据源对象列表#region 加载数据源对象列表
69 ArrayList ListTemplate = new ArrayList();
70 for (int ii = 0; ii < arrRealTime.Count; ii++)
71 {
72 TemplateList tl = arrRealTime[ii] as TemplateList;
73 ListTemplate.Add(new ListStatus(tl.TemplateTitleCn, tl.TemplateName));
74 }
75 #endregion
76
77 绑定模板下拉框#region 绑定模板下拉框
78 if (ListTemplate.Count != 0)
79 {
80 this.cmbTemplete.DisplayMember = "StatusName";
81 this.cmbTemplete.ValueMember = "StatusValue";
82 this.cmbTemplete.DropDownStyle = ComboBoxStyle.DropDownList;
83 this.cmbTemplete.DataSource = ListTemplate;
84 }
85 #endregion
86
87 if (ListTemplate.Count == 0)
88 {
89 this.btnSubmit.Enabled = false;
90 }
91 }
92}
这是一个窗体的加载事件,运行过程是这样,首先从系统模板配置文件中找到实时模板类型信息,然后验证该信息中指定的模板类型文件是否存在,如果存在的话把XML文件中的实时模板信息加载到对象列表中,然后通过该模板类型列表得到一个绑定下拉框的数据源对象列表,用来绑定下拉框。
上面函数中算很长的了,函数过长的一个原因是因为临时变量,因为临时变量只有在所属函数中才能使用,所以它会驱使你写出更长的函数,所以我们在开发过程中尽量少用或不用临时变量(可以使用一些重构准则实现)。函数过长的另一个原因是它承担了过多的职责,上面函数承担了多项职责:加载XML文件来获取模板信息对象列表;加载数据源对象列表;绑定模板下拉框等,我们首先把这三个职责抽取出来,分别用一个专职的函数来实现。
1/**//// <summary>
2/// 时实窗体类
3/// </summary>
4public partial class iRealTimeInfo : Form
5{
6 成员属性#region 成员属性
7 /**//// <summary>
8 /// 实时模板列表
9 /// </summary>
10 ArrayList arrRealTime = new ArrayList();
11 #endregion
12
13 public iRealTimeInfo()
14 {
15 InitializeComponent();
16 }
17
18 /**//// <summary>
19 /// 加载窗体事件
20 /// </summary>
21 private void iRealTimeInfo_Load(object sender, EventArgs e)
22 {
23 从XML文件中读取并获得所有曲线实时模板类型#region 从XML文件中读取并获得所有曲线实时模板类型
24 string strXmlFile = Application.StartupPath + @"\Templete\SystemTemplate";
25 XmlDocument doc = new XmlDocument();
26 doc.Load(strXmlFile + @"\SystemTemplate.xml");
27 foreach (XmlNode n in doc.ChildNodes)
28 {
29 if (n.Name == "Info")
30 {
31 foreach (XmlNode n2 in n.ChildNodes)
32 {
33 switch (n2.Name.ToLower())
34 {
35 case "realtime": //找到实时模板根结点
36 foreach (XmlNode n3 in n2.ChildNodes)
37 {
38 TemplateList TL = getTemplateModelByXmlNode(n3);
39
40 验证模板文件是否存在#region 验证模板文件是否存在
41 String sFilePath = strXmlFile + @"\" + n3.InnerText;
42 if (System.IO.File.Exists(sFilePath))
43 {
44 arrRealTime.Add(TL);
45 }
46 #endregion
47 }
48 break;
49 }
50 }
51 }
52 }
53 #endregion
54
55 ArrayList lstTemplate = LoadTemplateDataSource();
56
57 BindCboTemplate(lstTemplate);
58
59 if (lstTemplate.Count == 0)
60 {
61 this.btnSubmit.Enabled = false;
62 }
63 }
64
65 /**//// <summary>
66 /// 由XML获取模板对象
67 /// </summary>
68 /// <param name="xmlNode">XML模板结点</param>
69 private TemplateList getTemplateModelByXmlNode(XmlNode xmlNode)
70 {
71 TemplateList result = new TemplateList();
72
73 result.TemplateActc = "000000";
74 result.TemplateName = xmlNode.InnerText.Split('.')[0];
75 result.TemplateTitleCn = xmlNode.Attributes["name"].Value;
76 result.TemplateTitleEn = xmlNode.Attributes["nameEn"].Value;
77 result.TemplateAxisType = xmlNode.Attributes["type"].Value;
78 result.TemplateType = "0";
79 result.RealTimeHistory = "1";
80 result.ChartModel = xmlNode.Attributes["chartmodel"].Value;
81 result.DataType = xmlNode.Attributes["datatype"].Value;
82
83 return result;
84 }
85
86 /**//// <summary>
87 /// 绑定模板下拉框
88 /// </summary>
89 /// <param name="lstTemplate">模板数据源对象列表</param>
90 private void BindCboTemplate(ArrayList lstTemplate)
91 {
92 if (lstTemplate.Count != 0)
93 {
94 cmbTemplete.DisplayMember = "StatusName";
95 cmbTemplete.ValueMember = "StatusValue";
96 cmbTemplete.DropDownStyle = ComboBoxStyle.DropDownList;
97 cmbTemplete.DataSource = lstTemplate;
98 }
99 }
100
101 /**//// <summary>
102 /// 加载数据源对象列表
103 /// </summary>
104 private ArrayList LoadTemplateDataSource()
105 {
106 ArrayList result = new ArrayList();
107
108 for (int i = 0; i < arrRealTime.Count; i++)
109 {
110 TemplateList model = arrRealTime[i] as TemplateList;
111 result.Add(new ListStatus(model.TemplateTitleCn, model.TemplateName));
112 }
113
114 return result;
115 }
116}
通过上面的重构过程,加载事件看起来容易理解一点,但是对于XML文件的处理过程还是很复杂,另外,我们在此类中处理模板下拉框并显示实时模板数据的,而处理XML的职责也写在了此类中,所以此类也可以抽取出来,放在一个单独的类中,用来专门处理XML模板信息配置文件,最后处理结果如下:
1/**//// <summary>
2/// 处理模板配置xml文件类
3/// </summary>
4public class SystemTemplateManager
5{
6 /**//// <summary>
7 /// 模板目录
8 /// </summary>
9 private string directoryPath = "";
10
11 /**//// <summary>
12 /// 文档对象
13 /// </summary>
14 private XmlDocument xmlDoc = new XmlDocument();
15
16 /**//// <summary>
17 /// 系统模板管理类
18 /// </summary>
19 /// <param name="startupPath">启动路径</param>
20 public SystemTemplateManager(string startupPath)
21 {
22 this.directoryPath = startupPath + @"\Templete\SystemTemplate";
23 this.xmlDoc.Load(getFilePath("SystemTemplate.xml"));
24 }
25
26 公有方法#region 公有方法
27 /**//// <summary>
28 /// 获取实时模板列表
29 /// </summary>
30 /// <returns>返回实时模板对象列表</returns>
31 public ArrayList GetRealTimeTemplateList()
32 {
33 return GetTemplateList("realtime");
34 }
35
36 /**//// <summary>
37 /// 获取历史模板列表
38 /// </summary>
39 /// <returns>返回历史模板对象列表</returns>
40 public ArrayList GetHistoryTemplateList()
41 {
42 return GetTemplateList("history");
43 }
44 #endregion
45
46 私有方法#region 私有方法
47 /**//// <summary>
48 /// 获取实时模板列表
49 /// </summary>
50 /// <returns>返回实时模板对象列表</returns>
51 private ArrayList GetTemplateList(string type)
52 {
53 ArrayList result = new ArrayList();
54
55 foreach (XmlNode node in getInfoNode().ChildNodes)
56 {
57 if (node.Name.ToLower() == type)
58 {
59 foreach (XmlNode tNode in node.ChildNodes)
60 {
61 TemplateList model = getTemplateModelByXmlNode(tNode);
62 //做文件是否存在的验证。。。。。。。。。。
63 if (System.IO.File.Exists(getFilePath(tNode.InnerText)))
64 {
65 result.Add(model);
66 }
67 }
68 }
69 }
70
71 return result;
72 }
73
74 /**//// <summary>
75 /// 获取指定文件的物理路径
76 /// </summary>
77 /// <param name="fileName">文件名称</param>
78 /// <returns>返回指定文件的物理路径</returns>
79 private string getFilePath(string fileName)
80 {
81 return directoryPath + @"\" + fileName;
82 }
83
84 /**//// <summary>
85 /// 根结点
86 /// </summary>
87 /// <returns>返回根结点对象</returns>
88 private XmlNode getInfoNode()
89 {
90 return xmlDoc.SelectSingleNode("Info");
91 }
92
93 /**//// <summary>
94 /// 由XML获取模板对象
95 /// </summary>
96 /// <param name="xmlNode">XML模板结点</param>
97 private TemplateList getTemplateModelByXmlNode(XmlNode xmlNode)
98 {
99 TemplateList result = new TemplateList();
100
101 result.TemplateActc = "000000";
102 result.TemplateName = xmlNode.InnerText.Split('.')[0];
103 result.TemplateTitleCn = xmlNode.Attributes["name"].Value;
104 result.TemplateTitleEn = xmlNode.Attributes["nameEn"].Value;
105 result.TemplateAxisType = xmlNode.Attributes["type"].Value;
106 result.TemplateType = "0";
107 result.RealTimeHistory = "1";
108 result.ChartModel = xmlNode.Attributes["chartmodel"].Value;
109 result.DataType = xmlNode.Attributes["datatype"].Value;
110
111 return result;
112 }
113 #endregion
114}
115
116/**//// <summary>
117/// 实时窗体类
118/// </summary>
119public partial class iRealTimeInfo : Form
120{
121 成员属性#region 成员属性
122 /**//// <summary>
123 /// 实时模板列表
124 /// </summary>
125 ArrayList arrRealTime = new ArrayList();
126 #endregion
127
128 public iRealTimeInfo()
129 {
130 InitializeComponent();
131 }
132
133 /**//// <summary>
134 /// 加载事件
135 /// </summary>
136 private void iRealTimeInfo_Load(object sender, EventArgs e)
137 {
138 this.arrRealTime = new SystemTemplateManager(Application.StartupPath).GetRealTimeTemplateList();
139
140 ArrayList lstTemplate = LoadTemplateDataSource();
141 BindCboTemplate(lstTemplate);
142
143 if (lstTemplate.Count == 0)
144 {
145 this.btnSubmit.Enabled = false;
146 }
147 }
148
149 /**//// <summary>
150 /// 获取模板对象列表
151 /// </summary>
152 /// <returns>返回模板对象列表</returns>
153 private ArrayList LoadTemplateDataSource()
154 {
155 ArrayList result = new ArrayList();
156
157 for (int i = 0; i < arrRealTime.Count; i++)
158 {
159 TemplateList model = arrRealTime[i] as TemplateList;
160 result.Add(new ListStatus(model.TemplateTitleCn, model.TemplateName));
161 }
162
163 return result;
164 }
165
166 /**//// <summary>
167 /// 绑定模板下拉框
168 /// </summary>
169 /// <param name="lstTemplate">模板对象列表</param>
170 private void BindCboTemplate(ArrayList lstTemplate)
171 {
172 if (lstTemplate.Count != 0)
173 {
174 cmbTemplete.DisplayMember = "StatusName";
175 cmbTemplete.ValueMember = "StatusValue";
176 cmbTemplete.DropDownStyle = ComboBoxStyle.DropDownList;
177 cmbTemplete.DataSource = lstTemplate;
178 }
179 }
180}
当我们遇到过长的函数或者需要注释才能让人理解的代码块的时候,就应该考虑可不可以使用重构提取函数,不用管函数有多长,哪声只有一句,只要可以强化代码的清晰度,那就去做。就算提取出来的函数名称比函数体还长也无所谓。
过长的函数和嵌套过深的代码块(比如if、for、while和try代码块)是使函数更难于理解和维护的密不可分的两大元凶(而且经常毫无必要)。
我们不必担心函数拆分过多的问题,函数调用的开销可以忽略不计,使用小函数有以下几个好处:
1 如果每个函数的粒度都很小,那么函数之间彼此复用的机会就更大;
2 使得高层代码读起来就像是一系列注释;
3 如果函数都很小,那么函数的覆写就比较容易;
在命名函数的时候,每个函数都应该是顾其名而能思其义,我们应该以它“做什么”来命名,而不是以它“怎么做”命名,只有在你可以给小函数很好地命名时,它们才能真正起作用。