当前位置: 首页 > 工具软件 > Glade To Code > 使用案例 >

《Clean Code》读后感之选择一家好的公司至关重要

商鸿哲
2023-12-01

文中代码出现home、know等名词都是化名

        在不同的页面中,有不同的数据需要导出Excel。首先不得不感概一下,目前公司项目代码封装的好,书写流畅,关于导出Excel这一块已经封装到实现很简单了

        下面是截取之前已有的一段导出Excel的代码,方便文章的阅读我加上相应的注释

public IActionResult OldExcelExportPag1(long id)
{
    //为excel取一个名字
    var fileName = DateTime.Now.ToString("yyyyMMddhhmmss") + ".success.xlsx";

    //_homeRepo是个接口,通过id去获取数据源
    //数据源通过已封装的ToExcel去转换数据并映射为Excel的列
    var result = _homeRepo.ExportSuccess(id).ToExcel(cfg => cfg
        .Map(x => x.PostedOn, "Created on")
        .Map(x => x.Phone, "Phone")
        .Map(x => x.Street, "Street Address")
        .Map(x => x.City, "City")
        .Map(x => x.CountryCode, "Country"));

    //调用File,输入Excel的数据源、ContentType、文件名导出
    return File(result, "application/vnd.ms-excel", fileName);
}

        我的第一版与上面相同,此时的想法是先以实现为主。和之前的代码相比,只有5处优点

  • 命名上用source代替result,result的含义宽泛,什么行为的result?结合语境来看,这个方法是导出,那result就代表导出的结果,但到这一步,并没有导出,因此在这里使用没有名副其实,还需要人去揣摩。
  • 一行做一件事,长语句分开,所以有source,有format
  • 一行做一件事,长语句分开,所以Export和EachDo分开两行对齐
  • map的部分后面的字符串保持统一的缩进
  • fileName使用C#新的语法$,省去了字符串间用+(most of PD know it, but who cares
public IActionResult MyExport(long homeId)
{
     var fileName = $"homeNbr{homeId}Home{DateTime.Now:yyyyMMddhhmmss}.xlsx";

     var source = _homeRepo
                  .ExportFamilyHomes(homeId)
                  .EachDo(x => x.know = "is not ready", x => x.Should);

     var format = source.ToExcel(cfg => cfg
         .Map(x => x.RefNum,           "RefNumber")
         .Map(x => x.Num1,             "Num1#")
         .Map(x => x.Num2,             "Num2 #")
         .Map(x => x.Num2Original,     "Original Num2 #"));

     return File(format, "application/vnd.ms-excel", fileName);
}

        在第一版实现的过程中或是实现后,我思考的是下面的问题

        首先给我的感觉是,太顺溜了,太过程化了。这个方法中的语句是否是同一个抽象层级,我分析每个步骤的目的如下

  1. 设置Excel文件名
  2. 获取数据源
  3. 对数据源进行加工
  4. 设置Excel数据源格式
  5. 导出

        1和4是对Excel相关设置信息的细节描述,2和3是对数据源的细节描述,5是最终的目的,是导出,而我这个方法就是要导出,因此5层级最高,不care数据做了什么细节和怎么设置的excel,如果是下面的逻辑会不会更好

  1. 有数据源
  2. 有一个Excel的的导出工具
  3. 导出

        第二,在每一个导出的方法里最后都会写ContentType的这个字符串”application/vnd.ms-excel”,很明显抽出来容易修改、扩展、不易错……


我的第二版

exporter是在File才用到,为什么在source前创建?

public IActionResult MyExport(long homeId)
{
    var exporter = new Excels.Home();
    var source = _homeRepo
                 .ExportFamilyHomes(homeId)
                 .EachDo(x => x.know = "is not ready", x => x.Should);

    return File(exporter.GetFormat(source), ExcelContentType.ContentType, exporter.GetFileName(homeId));
}

        为了实现第二版新加以下接口

        为了直接返回一个名字或者通过传入一个参数来生成一个名字

public interface IExcelFileName
{
    string GetFileName();
    string GetFileName<T>(T param);
}

        为了获取数据格式(事实上这里的命名并不是完全准确,因为底层的方法ToExcel的方法做了两件事,将IEnumerable的数据源转换为Excel的数据源,并映射到相应的列名。而这个GetFormat返回的结果是Excel的数据源,但是其中调用ToExcel方法是看不到转换的过程,反而是看到每一个列的映射过程,因此取名为GetFormat)

interface IExcelFormat<T>
{
    byte[] GetFormat(IEnumerable<T> source);
}

        复用一个固定的字符串来当作Excel生成的ContentType

public class ExcelContentType
{
    internal static string ContentType = "application/vnd.ms-excel";
}

        我最终的目的是需要一个导出的工具,里面做好了导出需要做的任何细节,所以是一个总的接口继承多个小接口,接口隔离原则,保持每个接口尽可能的小

interface IExcelExporter<T> : IExcelFileName, IExcelFormat<T>
{
}

        实现这个页面需要导出的具体细节,有点策略模式的味道。每个导出的地方,都需要FileName,但FileName不同;都需要Format,但Format不同。使用的时候通过interface创建各自需要的implement

        GetFileName方法在这里并没有用到,因此实现throw NotImplementedException,如果误使用,直接的抛出错误提醒,远比返回string.Empty好,谙含“使用异常而非错误码

public class Home : IExcelExporter<Family.Home>
{
      public string GetFileName()
      {
          throw new NotImplementedException();
      }

      public string GetFileName<T>(T param)
          => $"homeNbr{param}Home{DateTime.Now:yyyyMMddhhmmss}.xlsx";

      public byte[] GetFormat(IEnumerable<Family.Home> source)
          =>  source.ToExcel(cfg => cfg
             .Map(x => x.RefNum,       "RefNumber")
             .Map(x => x.Num1,         "Num1#")
             .Map(x => x.Num2,         "Num2 #")
             .Map(x => x.Num2Original, "Original Num2 #"));
}

我的第三版

public IActionResult MyExport(long homeId)
{
    var source = _homeRepo.ExportFamilyHomes(homeId);
    var exporter = new Excels.Home {Source = source};

    return File(exporter.GetFormat(), ExcelContentType.ContentType, exporter.GetFileName(homeId));
}

        新增和变动接口如下

        新增一个Generics接口用于贮存数据,在内部处理细节

interface IExcelSource<T>
{
    IEnumerable<T> Source { set; }
}

        数据已然贮存在内部,一气呵成的处理,去掉该接口方法中的参数,不需要方法注入了

interface IExcelFormat
{
    byte[] GetFormat();
}
interface IExcelExporter<T> : IExcelSource<T>, IExcelFileName, IExcelFormat
{
}

        数据在传入后,不再允许被获取,对外不提供get访问

public class Home : IExcelExporter<Family.Home>
{
    public IEnumerable<Family.Home> Source { private get; set; }

    public string GetFileName()
    {
        throw new NotImplementedException();
    }

    public string GetFileName<T>(T param)
        => $"homeNbr{param}Home{DateTime.Now:yyyyMMddhhmmss}.xlsx";

    public IEnumerable<Family.Home> Process()
        => Source.EachDo(x => x.know = "is not ready", x => x.Should);

    public byte[] GetFormat()
        =>  Process()
           .ToExcel(cfg => cfg
                .Map(x => x.RefNum,               "RefNumber")
                .Map(x => x.Num1,                 "Num1#")
                .Map(x => x.Num2,                 "Num2 #")
                .Map(x => x.Num2Original,         "Original Num2 #"));
}

怎么解决下面两个问题?

  1. Home是继承过接口。怎么不使用new的方式可以创建Home实例?
  2. 为了封装数据内部处理,创建了IExcelSource,但是创建的Home对象,外部可以点出Source这个属性,可以看到?

感受:

  1. 并非我们不聪明,只是我们见得少。这个年龄都有一颗Postive的心,正像Peter说的,只是好的东西我们见得少而已
  2. 还是需要选公司。《Clean Code》第一章Attitude提到下面一段话

We blather about stupid managers and intolerant customers and useless markerting types and telephone sanitizers. But the fault, dear Dillbert, is not in our stars, but in ourselves. We are unprofessional.
代码变得糟糕把问题归咎于愚蠢的经理、苛求的用户……是我们太不专业了。难度不关进度的事?难度不关那些愚蠢的经理和没用的营销手段的事?难道他们就不该负点责吗?不。

        我对这段话抱着“你真幸运“的态度,潜在台词是”你说的真好听

        为什么要选公司,以上家公司来看,每周迭代不完的需求,不注重代码的整洁在那时那刻确实是开发最快的选择,成片的copy,UserController里每多一个页面涉及到注册会加一个Action,then复制吧……。你会说现在整洁以后会快,maybe you’re right。那我想说当你以工龄三年底层码农的身份拿着这份钱做这份事,连CEO都明确开会掷地有声的说不求好,能用,求快……,加上管理不当导致经常加无意义的班以及人员更迭频繁

        我想Uncle Bob在年轻时候的工作一定不是在这样一家公司,在这样的环境下,还有时间去优化?还有时间去思考、写书?呵呵哒,洗衣服做家务照顾猫咪和wife的时间都需要挤出来的。以我在他的Twitter和书中表达出来的语气来看,他必定会很赞同我这个观点,选择一家好的公司至关重要

        In the end of this blog我还想说为什么选择一家好的公司很重要。目前的公司开发不管从前到后很顺畅;我有时间去思考怎么可以做的更好;我可以接触一些居然百度搜索不到的pattern去学习然后提交到GitHub,这些都是多么幸运和合理的事情

I’m proud of I did something at afternoon. I’m glad I work at a good company. This kind of company won’t destory your work life balance at the most of time. It doesn’t get you into a never-ending spiral. It allows you to see a new landscape. That’s my definition of good. —Eden

 类似资料: