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

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

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

避免过多的参数

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

例:

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

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

		/// <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("文件保存失败");
            }
        }

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

  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

猜你喜欢

转载自blog.csdn.net/qq_32777817/article/details/86504080