写出优雅代码(C#)篇

作者:网络 时间: 2020-10-16

# 论题一:函数越小越好!

相信绝大部分程序员会认同这一点,维护一个超过 100 行的函数会让人抓狂。

我记得我以前修改过一个用 cobol 写的程序,一个文件超过 10 万行,我为了进行一个极其小的修改花了 3 天的时间,而且最后自己也不知道会不会造成什么严重的后果。

那么理想状态下我们的函数应该不大于多少行?我们三个人的答案是:

A: 10 行

B: 15 行

C: 20 行

# 论题二:用 Linq 简化代码

Linq 有时可以帮助我们写出一些非常“人性”的语句。

 public static void Create(IEnumerable<CommentData> Comments, SqlConnection cn){// validate paramsif (null == cn) throw new ArgumentNullException("cn");if (cn.State != ConnectionState.Open) throw new ArgumentException("Invalid parameter: connection is not open.", "cn");if (null == Comments) throw new ArgumentNullException("Comments");foreach (CommentData data in Comments){if (data.CommentId.HasValue)throw new ArgumentNullException("Create is only for saving new data.  Call save for existing data.", "data");}

....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

其中 foreach 这一部分可以简化为

 if (Comments.Any(data => data.CommentId.HasValue)){throw new ArgumentNullException("Create is only for saving new data.  Call save for existing data.", "data");}
1
2
3
4
5
6
7

在这一点上,我们存在分歧,A 认为没有必要进行简化,因为原来的已经很明确了;但 B 认为简化后的代码可读性更强,看上去更加直接。

# 论题三:集合初始值

希望每个人都已经知道 C#的这个用法了,直接上一些代码:

3.1

原始代码:

 List<int> idsToFind = new List<int>();

idsToFind.Add(1);

idsToFind.Add(2);
1
2
3
4
5

修改后:

List<int> idsToFind = new List<int> {1, 2};
1

3.2

原始代码:

var startingPoint = new Point();

startingPoint.X = 5;

startingPoint.Y = 13;
1
2
3
4
5

修改后:

 var startingPoint = new Point() { X = 5, Y = 13 };
1

# 论题四:运用 ?:和??

据说,有些公司会拿这个来测试入门的程序员:

4.1

原始代码:

 if (c != null)

  System.Console.WriteLine(c.Name);

else

  System.Console.WriteLine("List element has null value.");
1
2
3
4
5
6
7

修改后:

System.Console.WriteLine(c != null ? c.Name : "List element has null value.");
1

4.2

原始代码:

 string name = value;

if (value == null)

{

name = string.Empty;

}
1
2
3
4
5
6
7
8
9

修改后:

string name = (value != null) ? value : string.Empty;
1

还可以更简单,变成:

 string name = value ?? string.Empty;
1

# 论题五: 运用 AS

原始代码:

if (employee is SalariedEmployee)
{

  var salEmp = (SalariedEmployee)employee;

  pay = salEmp.WeeklySalary;

  // ...

}
1
2
3
4
5
6
7
8
9
10

修改后:

 var salEmployee = employee as SalariedEmployee;

if (salEmployee != null)

{

  pay = salEmployee.WeeklySalary;

  // ...

}
1
2
3
4
5
6
7
8
9
10
11

# 论题六: 运用 using

using 首次出现是在 visual studio 2005 中,在这以前,很多程序员晕倒在了释放资源的逻辑中。

使用 using 语句实际上生成的 IL 代码中是一个 try, finally 代码块,在 finally 代码块里释放资源。

原始代码:

 public IEnumerable<Order> GetOrders()

{

  var orders = new List<Order>();

  var con = new SqlConnection("some connection string");

  var cmd = new SqlCommand("select * from orders", con);

  var rs = cmd.ExecuteReader();

  while (rs.Read())
  {// ...
  }

  rs.Dispose();

  cmd.Dispose();

  con.Dispose();

  return orders;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

这是一段非常丑陋的代码,我们完全迷失在 dispose 群中,什么时候要调用哪个 dispose 啊? 天哪? 如果我们用 finally, 可以将代码写为:

public IEnumerable<Order> GetOrders()

{

  SqlConnection con = null;

  SqlCommand cmd = null;

  SqlDataReader rs = null;

  var orders = new List<Order>();

  try

  {

​    con = new SqlConnection("some connection string");

​    cmd = new SqlCommand("select * from orders", con);

​    rs = cmd.ExecuteReader();while (rs.Read()){// …}

  }

  finally

  {

​    rs.Dispose();
c
​    cmd.Dispose();

​    con.Dispose();

  }

  return orders;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

看看 using 到底给我们带来了什么:

public IEnumerable<Order> GetOrders()
{
  var orders = new List<Order>();

  using (var con = new SqlConnection("some connection string"))
  {using (var cmd = new SqlCommand("select * from orders", con)){using (var rs = cmd.ExecuteReader()){while (rs.Read()){// ...}}}
  }
  return orders;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

好多了,对吗? 完全不用再用那一堆的 try/finally 代码了,也不用使用一堆的 null,为了使代码更轻巧,让我们再做小小修改:

public IEnumerable<Order> GetOrders()
{
  var orders = new List<Order>();

  using (var con = new SqlConnection("some connection string"))

  using (var cmd = new SqlCommand("select * from orders", con))

  using (var rs = cmd.ExecuteReader())

  {while (rs.Read()){// ...}

  }

  return orders;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 论题七:命名规范

也许有人认为没有必要再提这个问题,但在日常编码生活中,这的确是一个很重要的话题。

7.1 类名、方法、常数使用 Pascal casing

public class MyClass

{

  const int DefaultNumber = 100;

  public void MyMethod()

  { }

}
1
2
3
4
5
6
7
8
9
10
11

7.2 局部变量,参数用 camel casing

partial void OnContactIdChanging(int value)
{
	int number;
}
1
2
3
4

7.3 interface 名字以 I 开头

7.4 尽量不用单个字符命名变量,象 i 或者 t 。使用 index 或者 temp 之类代替。

7.5 将所有来自 framework 的 namespace 放在前面,而后再放第三方或自定义的:

using System;

using System.Linq;

using System.Data.Linq;

using System.Collections.Generic;

using System.Text;

using System.ComponentModel.DataAnnotations;

using CodeSmith.Data.Attributes;

using CodeSmith.Data.Rules;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 论题八: 一个方法的参数不能超过 5 个

当多于 5 个时,应进行函数的拆分或者参数的封装。– 嚯嚯就像论题一样的规定

一些说明:不是为了给自己一个紧箍咒,而是在日常编程中,我们发现如果你写的方法不满足这样一个条件,一年后,就算是你自己也不太想去维护和修改,如果换成是其他程序员会对此更加的头痛,对吗?

# 论题九: 不要滥用注释

有些非常清晰明确的代码不需要注释,仅在必要的时候注释你的代码,不要太多,并且注释也要简单给力。

# 论题十: 不要把数值 hard-code 在代码中

使用 const 来定义

# 论题十一: 不要使用””, 使用 string.Empty

正确的:

string name = string.Empty;

不建议:

string name = "";

论题十二: 善于合并 if

观察下面这段可爱的代码:

public bool Equals(CommentData obj) {

   if (!CommentId.Equals(obj.CommentId)) return false;

   if (!Comment.Equals(obj.Comment)) return false;

   if (!CommentorId.Equals(obj.CommentorId)) return false;

   return true;

  }
1
2
3
4
5
6
7
8
9
10
11

如果我们写成这样会不会好些呢:

public bool Equals(CommentData obj) {

   return CommentId == obj.CommentId &&

​       Comment.Equals(obj.Comment) &&

​       CommentorId == obj.CommentorId;

  }
1
2
3
4
5
6
7
8
9

论题十三: 不断重构你的代码

当有新的需求或新改动的时候,可以拨一些时间来重构。你可能突然发现,原来重构后的代码可以如此美丽。使用一些重构的插件,比如 resharper 可以使你事半功倍。