您的位置:首页 > 编程语言 > C#

如何写出优雅漂亮的c#代码_方法(三)

2019-01-16 15:05 387 查看

今天我们来讲如何把方法写的干净漂亮,其实方法写好并不难,主要注意以下几点。

  • 避免过多的参数
  • 避免过长的方法体
  • 正确的访问级
  • 合理的定义返回值

避免过多的参数

参数过多会导致程序代码冗长,不仅仅调用方会蒙圈。如果方法参数有修改更是一件很痛苦的事情。

例:

定义一个方法用来保存药品,药品包括“品名,规格,生产厂商,适应症,用法用量,注意事 项,不良反应,有效期,有效期至,批次,批准文号”,等信息。(此处仅作为举例,真正的药品属性要比这多得多,此处不一一列举)

经验不足的小伙伴很容易把方法写成这样。

/// <summary>
/// 添加药品
/// </summary>
/// <param name="name">名称</param>
/// <param name="spec">规格</param>
/// <param name="manufacturer">生产厂商</param>
/// <param name="indication">适应症</param>
/// <param name="usageDosage">用法用量</param>
/// <param name="mattersNeedingAttention">注意事项</param>
/// <param name="untowardEffect">不良反映</param>
/// <param name="expiryDate">有效期</param>
/// <param name="vld">有效期至</param>
/// <param name="batchNumber">批次</param>
/// <param name="LicenceNo">批准文号</param>
/// <returns></returns>
private static bool AddDrug(
string name,
string spec,
string manufacturer,
string indication,
string usageDosage,
string mattersNeedingAttention,
string untowardEffect,
int expiryDate,
DateTime vld,
string batchNumber,
string LicenceNo)
{
return true;
}

虽然做法没什么问题,参数名称很清楚,还加了相应的注释。但真正调用方法并传参的时候,你会发现这种方法并不友好。

这么多的参数,每一个对应的写起来,真的很痛苦。如果有一个参数传错了,你需要检查所有的参数,假设这个方法有30个参数,检查参数所耗费的时间精力就会翻倍,而这种写法的缺点就是很容易造成传参错误,当然你依然可以用以下方式调用,但这并不是我们最推荐的方法。

static void Main(string[] args)
{
string name = "伟哥";
string spec = "";
string manufacturer = "";
string indication = "治疗男子**勃起功能障碍";
string usageDosage = "";
string mattersNeedingAttention = "";
string untowardEffect = "";
int expiryDate = 1;
string batchNumber = "";
string LicenceNo = "";
DateTime vld = new DateTime(2021, 1, 10);

AddDrug(
name: name,
spec: spec,
manufacturer: manufacturer,
indication: indication,
usageDosage: usageDosage,
mattersNeedingAttention: mattersNeedingAttention,
untowardEffect: untowardEffect,
expiryDate: expiryDate,
vld: vld, batchNumber:
batchNumber,
LicenceNo: LicenceNo
);
}

在这里我推荐如果方法参数超过5个一定要写成类,有以下几点好处。

  1. 不会造成方法参数的冗长,可读性高
  2. 修改方法参数,只要修改对应类中的属性即可,不会造成编译错误。
  3. 参数类的构造函数,get;set;属性还可以对参数进行初步的验证。
static void Main(string[] args)
{
Drug drug = new Drug();
drug.name = "伟哥";
drug.spec = "";
drug.manufacturer = "";
drug.indication = "治疗男子**勃起功能障碍";
drug.usageDosage = "";
drug.mattersNeedingAttention = "";
drug.untowardEffect = "";
drug.expiryDate = 1;
drug.batchNumber = "";
drug.LicenceNo = "";
drug.vld = new DateTime(2021, 1, 10);
AddDrug(drug);
}

private static bool AddDrug(Drug drug)
{
return true;
}

避免过长的方法体

例:

将Drug类序列化为xml,并保存到本地磁盘。

//以下代码仅作为逻辑讲解使用,并未真正调试并运行成功,请见谅
static void Main(string[] args)
{
Drug drug = new Drug();
drug.name = "伟哥";
drug.spec = "";
drug.manufacturer = "";
drug.indication = "治疗男子**勃起功能障碍";
drug.usageDosage = "";
drug.mattersNeedingAttention = "";
drug.untowardEffect = "";
drug.expiryDate = 1;
drug.batchNumber = "";
drug.LicenceNo = "";
drug.vld = new DateTime(2021, 1, 10);

string str = string.Empty;

try
{
MemoryStream Stream = new MemoryStream();
XmlSerializer xml = new XmlSerializer(typeof(Drug));
//序列化对象
xml.Serialize(Stream, drug);
Stream.Position = 0;
StreamReader sr = new StreamReader(Stream);
str = sr.ReadToEnd();

sr.Dispose();
Stream.Dispose();
}
catch (Exception ex)
{
Console.WriteLine("序列化失败");
return;
}

try
{
FileStream fs = new FileStream(@"D:\drug\drug.xml", FileMode.OpenOrCreate, FileAccess.ReadWrite);
StreamWriter sw = new StreamWriter(fs);
if (sw != null)
{
sw.WriteLine(str);
sw.Close();
sw.Dispose();
}
if (fs != null)
{
fs.Close();
fs.Dispose();
}
}
catch (Exception)
{
Console.WriteLine("文件保存失败");

4000
}
}

经验不足小伙伴经常写出这种代码,导致方法体冗长。
接下来我们具体分析以下,本方法主要包含三个主要逻辑

  1. 创建Drug类
  2. 序列化drug
  3. 创建本地xml

按照这三个功能点,我们继续优化。

static void Main(string[] args)
{
//创建drug
//我们需要的是一个Drug的实体,具体drug怎么来的main方法并不关系,直接交给CreateDrug就够了
Drug drug = CreateDrug();
try
{
//生成xml
//在一个完整的项目中不可能只有一个类需要序列化为xml,所以我们要定义的通用些
string xml = GenerateXml(typeof(Drug), drug);
//保存xml
//如何保存,用那种方式保存,io如何操作,文件流创建,关闭,销毁,对main来说并不关心,
//main只需要保存方法,其他的问题是SaveXml方法的事情
SaveXml(xml, @"D:\drug\drug.xml");
}
//这个异常处理并不完善,后续我们讲详细讲述如何玩好try catch
catch
{
}
}
private static Drug CreateDrug()
{
Drug drug = new Drug();
drug.name = "伟哥";
drug.spec = "";
drug.manufacturer = "";
drug.indication = "治疗男子**勃起功能障碍";
drug.usageDosage = "";
drug.mattersNeedingAttention = "";
drug.untowardEffect = "";
drug.expiryDate = 1;
drug.batchNumber = "";
drug.LicenceNo = "";
drug.vld = new DateTime(2021, 1, 10);
return drug;
}

private static string GenerateXml(Type t, object model)
{
string str = string.Empty;
try
{
MemoryStream Stream = new MemoryStream();
XmlSerializer xml = new XmlSerializer(t);
//序列化对象
xml.Serialize(Stream, model);
Stream.Position = 0;
StreamReader sr = new StreamReader(Stream);
str = sr.ReadToEnd();

if (sr != null)
{
sr.Dispose();
Stream.Dispose();
}
}
catch (Exception ex)
{
throw;
}
return str;
}

private static void SaveXml(string xml, string path)
{
try
{
FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite);
StreamWriter sw = new StreamWriter(fs);
if (sw != null)
{
sw.WriteLine(xml);
sw.Close();
sw.Dispose();
}
if (fs != null)
{
fs.Close();
fs.Dispose();
}
}
catch (Exception)
{
throw;
}
}

我们看到了,main方法中干净了不少。main只负责调用,而不去管具体的实现逻辑,实现逻辑交给三个子方法,这也就意味着,逻辑变动不会影响到调用,程序的可维护性大大提高。
所以,一个方法的逻辑内容超过50行那一定需要优化了(这里强调下,不是说不超过50就不需要优化,将不同的逻辑内容拆成不同的逻辑方法是个非常好的习惯),可以按照以上逻辑,分功能拆出不同的子逻辑,大大提高了可读性,也提高了可维护性,何乐而不为。

正确的访问级

例:

定义Car类,Car是一辆可以自动驾驶的汽车,Car需要对外公开Start(),Run(),Stop()方法供外界调用,同时Satrt,Run,Stop的内部逻辑也要实现。

public class Car
{
/// <summary>
/// 启动
/// </summary>
public void Start()
{
Electrify();
StarterRun();
CarburetorRun();
EngineRun();
//启动完成,随时可以狂奔

}
/// <summary>
/// 行驶
/// </summary>
public void Run()
{
SteppingOnClutch();
HangingGear();
Throttle();
//2倍音速狂奔中
}
/// <summary>
///停车
/// </summary>
public void Stop()
{
Brake();
Stall();
//这颗难以压抑的心也需要熄火
}
/// <summary>
/// 通电
/// </summary>
public void Electrify() { }
/// <summary>
/// 起动机工作
/// </summary>
public void StarterRun() { }
/// <summary>
/// 化油器工作
/// </summary>
public void CarburetorRun() { }
/// <summary>
/// 引擎工作
/// </summary>
public void EngineRun() { }
/// <summary>
/// 离合器
/// </summary>
public void SteppingOnClutch() { }
/// <summary>
/// 挂挡
/// </summary>
public void HangingGear() { }
/// <summary>
/// 油门
/// </summary>
public void Throttle() { }
/// <summary>
/// 刹车
/// </summary>
public void Brake()
{

}
/// <summary>
/// 熄火
/// </summary>
public void Stall() { }

}

好了,我们已经实现了。可以看到,基本上每一个步骤我们都实现了,接下来我们是不是可以愉快的让别人去开了,但是问题来了。

f**k,你不是一个很牛B的AI自动驾驶吗,挂挡、刹车、离合、甚至化油器启动机都要我自己操作,这个自动驾驶不过如此、、、于是出现了以下代码。

static void Main(string[] args)
{
Car aNBCar = new Car();
aNBCar.Electrify();
aNBCar.StarterRun();
aNBCar.CarburetorRun();
aNBCar.EngineRun();
//启动完成,随时可以狂奔
aNBCar.SteppingOnClutch();
aNBCar.HangingGear();
aNBCar.Throttle();
//2倍音速狂奔中
aNBCar.Brake();
aNBCar.Stall();
//这颗难以压抑的心也需要熄火
}

这时你会说:你只要直接Start(),就好了。
f**k,既然start能搞定,你给我这么多东西干啥?

因为访问级别的不明确导致重复的逻辑,重复的代码,必然导致代码的冗余。public 只有对外公开时采用,正确的控制访问级,根据实际情况选择访问级别。

public class Car
{
/// <summary>
/// 启动
/// </summary>
public void Start()
{
Electrify();
StarterRun();
CarburetorRun();
EngineRun();
//启动完成,随时可以狂奔

}
/// <summary>
/// 行驶
/// </summary>
public void Run()
{
SteppingOnClutch();
HangingGear();
Throttle();
//2倍音速狂奔中
}
/// <summary>
///停车
/// </summary>
public void Stop()
{
Brake();
Stall();
//这颗难以压抑的心也需要熄火
}
/// <summary>
/// 通电
/// </summary>
private void Electrify() { }
/// <summary>
/// 起动机工作
/// </summary>
private void StarterRun() { }
/// <summary>
/// 化油器工作
/// </summary>
private void CarburetorRun() { }
/// <summary>
/// 引擎工作
/// </summary>
private void EngineRun() { }
/// <summary>
/// 离合器
/// </summary>
private void SteppingOnClutch() { }
/// <summary>
/// 挂挡
/// </summary>
private void HangingGear() { }
/// <summary>
/// 油门
/// </summary>
private void Throttle() { }
/// <summary>
/// 刹车
/// </summary>
private void Brake()
{

}
/// <summary>
/// 熄火
/// </summary>
private void Stall() { }

}

启动、挂挡、踩离合、给油门这种事就不要让外人掺和了。


好的,伙计,我知道该咋办了。

static void Main(string[] args)
{
Car aNBCar = new Car();
aNBCar.Start();
aNBCar.Run();
aNBCar.Stop();
}

正确的定义访问级,不要让你的基友误解,使逻辑更清晰。

合理的定义返回值

例:
写一个CreateOrder方法,要求返回订单的Guid id,string order_no。

首先我们知道c#并不支持多返回值,所以经常有新人这样写。

public static string CreateOrder()
{
return "0123456789|EAB4B190-5784-467A-B26B-4B77FD4890AD";
}
static void Main(string[] args)
{
string order = CreateOrder();
string order_no = string.Empty;
Guid order_id = Guid.Empty;
//没有办法,既然是返回一定格式的字符串,那就只能先对字符串效验
if (string.IsNullOrWhiteSpace(order) && order.Contains('|'))
{
//分离成为数组
var ary = order.Split('|');
order_no = args[0];
string id = ary[1];
//为确保数据的合法性,还要对数据进行二次效验
if (Guid.TryParse(id, out order_id))
{

}
}
}

大家看到了,仅仅是取到返回值就写了这么多代码,这些代码完全可以看做在浪费时间,我们看下一种方法。

static void Main(string[] args)
{
Guid id = Guid.Empty;
string order_no = string.Empty;
CreateOrder(ref id, ref order_no);
}

public static void CreateOrder(ref Guid id, ref string order_no)
{
id = new Guid();
order_no = "0123456789";
}

OK,好的,没有了那么冗长的代码。但还有其他问题,如果要求返回订单金额怎么办?继续加ref?没有问题,但是每加一个ref就是增加一个方法参数,都会导致一次编译报错。这在团队开发是很不利的,我们继续优化。

static void Main(string[] args)
{
KeyValuePair<Guid, string> order = CreateOrder();
Guid id = order.Key;
string order_no = order.Value;
}
public static KeyValuePair<Guid, string> CreateOrder()
{
return new KeyValuePair<Guid, string>(Guid.NewGuid(), "0123456789");
}

这样的确不会导致参数变化了,但是并没有根本的解决增加返回值的问题,KeyValuePair只针对一对一的情况,如果这样呢。

static void Main(string[] args)
{
string order_no = string.Empty;
Guid id = Guid.Empty;
decimal amount = 0m;
string[] order = CreateOrder();
if (order.Length == 3)
{
return;
}
if (string.IsNullOrEmpty(order[0]))
{
order_no = order[0];
}
if (string.IsNullOrWhiteSpace(order[1]))
{
if (Guid.TryParse(order[0], out id))
{

}
}
if (decimal.TryParse(order[2], out amount))
{

}

}

public static string[] CreateOrder()
{
return new string[]
{
"0123456789",
"EAB4B190-5784-467A-B26B-4B77FD4890AD"
};
}

解决了多返回值的问题,但是又需要一大坨代码对返回值进行效验,我们需要的是优雅漂亮的代码,不妥不妥啊,我们还可以使用c# 的元组。

static void Main(string[] args)
{
Tuple<Guid, string,decimal> order = CreateOrder();
Guid id = order.Item1;
string order_no = order.Item2;
decimal amount = order.Item3;
}

public static Tuple<Guid, string, decimal> CreateOrder()
{
return new Tuple<Guid, string, decimal>(Guid.NewGuid(), "0123456789", 11.5m);
}

看上去已经很不错了,代码简单逻辑清晰。在c# 7.0中,甚至可以这样做。

static void Main(string[] args)
{
var (id, order_no, amount) = CreateOrder();
}
public static (Guid, string, decimal) CreateOrder()
{
return (Guid.NewGuid(), "0123456789", 11.5m);
}

这、这、这,这不就是我们要的如诗一般优美的代码么,但是缺点依然有,如果我们要求返回订单所有属性,共计100个,是不是有点坑,这时应该这样做。

public class Order
{
public Guid id { get; set; }
public string order_no { get; set; }
}
static void Main(string[] args)
{
Order order = CreateOrder();
}

public static Order CreateOrder()
{
return new Order();
}

好的,我们似乎一劳永逸了,哪怕有200个属性我们都不怕了。但是也有缺点,不分青红皂白的拿类做返回值,容易造成类文件爆炸的问题。

总结:

  • 避免过多的参数
    不要给方法定义太多的参数,如果真的需要很多参数,可以考虑使用类作为参数
  • 避免过长的方法体
    方法的内容不要过长,根据方法内部的逻辑模块拆分成对应的子方法
  • 正确的访问级
    该公开的公开,该封闭的封闭,以免引起歧义
  • 合理的定义返回值
    如果返回值是一个键值对,一对一,用KeyValuePair最合适不过了
    如果返回值只有三两个使用元组最好,推荐使用c# 7.0版本写法
    如果返回值很多推荐使用类返回

最后感谢大家,下一期我们介绍如何写class

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: