一个函数应该写多长?
最近在看公司源代码的时候,经常有一些超长函数出现,甚至超过1000多行的函数都大有存在,这大大影响我对代码的理解,所以写下此文,当然只是自己的想法,不强加于人,只为勉励自己。
在以往的软件开发中,对于函数我也是想写多长就写多长,从来不去想它有多长这个“无聊的问题”,因为对于一个函数应该写多长并没有一个确切的定义,这应该看具体情况决定。
我个人觉得,无论是类还是函数,都应该满足单一职责原则,如果出现一个函数过长或者代码块嵌套过深的情况,常常是因为没有满足这一原则,这两种情况都能够通过更好的重构来解决。
以我工作中的一个代码片段为例来看一下,函数写多长,如何拆分函数。
1

/**//// <summary>2
/// 时实窗体类3
/// </summary>4
public partial class iRealTimeInfo : Form5


{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
#endregion60
}61
break;62
}63
}64
}65
}66
#endregion67

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
#endregion76

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
#endregion86

87
if (ListTemplate.Count == 0)88

{89
this.btnSubmit.Enabled = false;90
}91
}92
}这是一个窗体的加载事件,运行过程是这样,首先从系统模板配置文件中找到实时模板类型信息,然后验证该信息中指定的模板类型文件是否存在,如果存在的话把XML文件中的实时模板信息加载到对象列表中,然后通过该模板类型列表得到一个绑定下拉框的数据源对象列表,用来绑定下拉框。
上面函数中算很长的了,函数过长的一个原因是因为临时变量,因为临时变量只有在所属函数中才能使用,所以它会驱使你写出更长的函数,所以我们在开发过程中尽量少用或不用临时变量(可以使用一些重构准则实现)。函数过长的另一个原因是它承担了过多的职责,上面函数承担了多项职责:加载XML文件来获取模板信息对象列表;加载数据源对象列表;绑定模板下拉框等,我们首先把这三个职责抽取出来,分别用一个专职的函数来实现。
1

/**//// <summary>2
/// 时实窗体类3
/// </summary>4
public partial class iRealTimeInfo : Form5


{6

成员属性#region 成员属性7

/**//// <summary>8
/// 实时模板列表9
/// </summary>10
ArrayList arrRealTime = new ArrayList();11
#endregion12

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
#endregion47
}48
break;49
}50
}51
}52
}53
#endregion54

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>4
public class SystemTemplateManager5


{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
#endregion45

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
#endregion114
}115

116

/**//// <summary>117
/// 实时窗体类118
/// </summary>119
public partial class iRealTimeInfo : Form120


{121

成员属性#region 成员属性122

/**//// <summary>123
/// 实时模板列表124
/// </summary>125
ArrayList arrRealTime = new ArrayList();126
#endregion127

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 如果函数都很小,那么函数的覆写就比较容易;
在命名函数的时候,每个函数都应该是顾其名而能思其义,我们应该以它“做什么”来命名,而不是以它“怎么做”命名,只有在你可以给小函数很好地命名时,它们才能真正起作用。
浙公网安备 33010602011771号