自顶向下的编程方法详解

什么是自顶向下的编程方法?

百度百科解释如下:

自顶向下的程序设计方法指的是首先从主控程序开始,然后按接口关系逐次分割每个功能为更小的功能模块,直到最低层模块设计完成为止。自顶向下是一种有序的逐步分层分解和求精的程序设计方法。其特点是层次清楚,编写方便,调试容易

  我们可以用更加通俗的语言来解释:

  所谓的自顶向下的编程方法,本质上就是编写程序的视角从整体的宏观性逐层进入具体的微观性的一种编程思想。我们编写程序时一开始不用思考得事无巨细,把所有细节都想清楚;也不要面条式的想到哪里写到哪里。而应该是自顶向下的,从一个大的粗的核心的任务开始,逐级细分,最后再完成最底层的具体实现。

自顶向下方法具体示例

  这样说可能还是过于抽象。我们用一个具体的例子来说明。

  假设有一个类负责处理数据并组织成Json对象供第三方图表库进行展现。这个类有个public方法负责从Excel中获取数据,并组织成Json。那么这个Public方法应该怎么写?

  按照传统的做法,可能立刻就开始思考采用什么库来读取Excel,Excel中的数据怎么获取,获取之后怎么组织成json,最后返回。并开始考虑如果拥护给的Excel地址不对怎么办,如果Excel本身有bug怎么办?并根据这些思考开始给代码加上if和else条件判断。不知不觉一个函数就已经有数百行代码了。

面条式写法缺点

  倒并不是说这样写程序不行,但是这样写出来的程序有几个很大的问题:

  1. 方法代码很长,很难维护。这种事无巨细,意大利面条式的代码长度一定短不了,如果业务逻辑更加复杂一点,长度可能会成倍数上涨。而长的代码对阅读来说是巨大的挑战,后期维护起来很难迅速理解并提出好的优化方案。
  2. 测试不友好,因为事无巨细,很多逻辑和类库混在一起,非常不利于单元测试。
  3. 编写过程思路不清晰,虽然前期经过了设计和思考,但是因为揉在一起,缺乏清晰的分层。编程过程是一个高强度的脑力劳动,时间一长,很难一直保持清醒,容易不小心引入bug。

自顶向下编程方法思考过程

  那么自顶向下的编程思想鼓励我们怎么做呢?我们首先思考清楚整个类或者主函数最总要的任务是做什么,然后进一步细分需要几步,讲步骤抽象成函数,此时不需要函数的具体实现,只需要将函数命名成具体的实现相关即可。还是以上述例子来说明。
  首先这个方法是读取Excel并返回Json字符串,那么我们思考这个事情最少应该分成几步。通过思考,我们认为至少应该是3步:

  1. 找到这个Excel,返回Excel对象。
  2. 读取这个Excel,返回读取结果对象。
  3. 将结果对象转换成Json并返回。

  基于以上分析,我们确认了核心函数中的三大步,并故意忽略了通过什么方式去找Excel,通过什么库读取Excel,怎么读取Excel以及怎么转换成Json的细节。

using System.Dynamic;
public Class ReadDataCOnvertToJson{
    public string GetJsonDataFormExcel(string excelPath){
        //1. 找到这个Excel,返回Excel对象。
        Object excelObj = GetExcelObjet(excelPath);
        //2. 读取这个Excel,返回读取结果对象
        Object DataWaitConvertToJson = GetDataWaitConvertToJson(excelObj);
        //3. 将结果对象转换成Json并返回。
        return ChangeDataToJson(DataWaitConvertToJson);
    }
}

  此时,GetExcelObjet,GetDataWaitConvertToJson,ChangeDataToJson都没有实现,编译器上甚至还在报错(提示方法不存在),返回的Excel对象也还没有确定具体的类型,从Excel中读取的数据组成的类也没有确定具体的结构。但是这个程序我们已经“写完了”。因为这个方法最重要最核心的部分我们确实完成了,言简意赅,思路清晰明确。剩下的事情是其他的内部函数应该完成的事情了。

  此时,我们甚至已经可以开始为我们的GetJsonDataFormExcel方法编写单元测试了,我们很容易理解GetJsonDataFormExcel要做什么,此时编写单元测试是最佳时机。

  当然,我们此时还不能下班回家——编写的单元测试现在肯定是跑不通的。我们可以利用现代编译器强大的功能,让它帮我们生成一个空的GetExcelObjet,GetDataWaitConvertToJson,ChangeDataToJson。然后我们开始为这些方法填充具体的逻辑。

  此时,我们可以开始思考深层一点的问题,用什么库来读取这个Excel?读取这个Excel的时候如果根据地址找不到怎么办?返回的Excel对象是什么?然后我们按照这个线索来扩充GetExcelObjet方法。

    private ISheet GetExcelObjet(string excelPath){
        if(!ExcelIsExist(excelPath)){
            return null;
        }
        try
        {
            IWorkbook workbook = WorkbookFactory.Create(importExcelPath);
            ISheet sheet = workbook.GetSheetAt(0);//获取第一个工作薄
            return sheet;
        }
        catch (System.Exception ex)
        {
            log.info(ex)
            throw;
        }
    }

  我们选择使用NPOI库读取Excel,返回NPOI的ISheet接口。并在方法内部判断了Excel不存在的情况,做了异常处理。而Excel不存在的具体判断我们在这一层依旧不关心,放在下层中再具体细化处理。
  就这样,们再按照相同的方式逐步去实现其他的方法。

  这种逐层细化,逐层向下,从最开始的宏观框架到越来越明确细节的方式,就是自顶向下编程方法。

自顶向下编程方法的优点

  这样写代码的最大好处与先前描述的意大利面条式编码方式的坏处一一对应。

  1. 编码过程中思路更清晰,就像我们写高质量的文章一样,先有题目,再有大纲,再有二级大纲,最后才是正文内容。有了这些大纲的指引,我们可以暂时屏蔽繁琐的细节,关注任务的全貌,使得编写的代码结构清晰,不容易引入bug。
  2. 代码阅读更加容易,方便后期维护。阅读自顶向下编程方式写出来的代码,就像是看报纸,在阅读更多的细节之前,我们更希望大致了解报纸文章的大致中心思想。同理,在我们阅读一段代码时,如果能够大致了解它的核心顶层工作方式,能够更好的帮助我们理解。
  3. 因为代码的编写过程是自顶向下逐层细化的,我们在可以在编码的早期就形成了一个可以被单元测试的函数。这很符合测试框架的流程——测试框架不关心程序内部是如何实现的,只要能够根据传入的参数返回正确的结果即可。同理,我们早期在尚未真正实现底层函数时,可以通过mock或硬编码的形式,让单元测试得以通过,更早的将主方法或类放到单元测试的保护之下,再真正实现过程中,就可以更加游刃有余。
  4. 解决了很多人习惯性的在编码完成后,再去进行所谓的重构优化的难题。意大利面条式的代码一旦形成,想要拆分确实是难上加难,既然如此为什么不一开始就忽略细节,编写更好重构的代码?当然,并不是说自顶向下的编码方式就不用特意去重构了,当我们的最底层具体实现过长过于复杂的情况下,我们依然应该将其打碎,拆分,重命名,并放到合适的位置上。

自定向下思想的延伸

  自顶向下的思想除了在编程过程中有用以外,还可以运用到日常生活中很多其他的地方,如刚刚说过了写文章,再比如制定个人计划等等,我们人类天生就是擅长这种思考方式。希望这篇文章能够帮助到大家。

原文地址:https://www.cnblogs.com/wenpeng/p/12442069.html

时间: 2024-10-12 20:05:44

自顶向下的编程方法详解的相关文章

并发编程(六)Object类中线程相关的方法详解

一.notify() 作用:唤醒一个正在等待该线程的锁的线程 PS : 唤醒的线程不会立即执行,它会与其他线程一起,争夺资源 /** * Object类的notify()和notifyAll()方法详解 */ public class MyNotify { // 在多线程间共享的对象上使用wait private String[] shareObj = {"true"}; public static void main(String[] args) { MyNotify test =

【转】深入学习JavaScript: apply call方法 详解(转)

Js apply方法详解 原文:http://blog.csdn.net/myhahaxiao/article/details/6952321 我在一开始看到JavaScript的函数apply和call时,非常的模糊,看也看不懂,最近在网上看到一些文章对apply方法和call的一些示例,总算是看的有点眉目了,在这里我做如下笔记,希望和大家分享..  如有什么不对的或者说法不明确的地方希望读者多多提一些意见,以便共同提高.. 主要我是要解决一下几个问题: 1.        apply和cal

Python 字符串方法详解

Python 字符串方法详解 本文最初发表于赖勇浩(恋花蝶)的博客(http://blog.csdn.net/lanphaday),如蒙转载,敬请保留全文完整,切勿去除本声明和作者信息. 在编程中,几乎90% 以上的代码都是关于整数或字符串操作,所以与整数一样,Python 的字符串实现也使用了许多拿优化技术,使得字符串的性能达到极致.与 C++ 标准库(STL)中的 std::string 不同,python 字符串集合了许多字符串相关的算法,以方法成员的方式提供接口,使用起来非常方便. 字符

JAVA 注解的几大作用及使用方法详解

JAVA 注解的几大作用及使用方法详解 (2013-01-22 15:13:04) 转载▼ 标签: java 注解 杂谈 分类: Java java 注解,从名字上看是注释,解释.但功能却不仅仅是注释那么简单.注解(Annotation) 为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后 某个时刻方便地使用这些数据(通过 解析注解 来使用这些数据),常见的作用有以下几种: 1.生成文档.这是最常见的,也是java 最早提供的注解.常用的有@see @param @return 等:

四种生成和解析XML文档的方法详解(介绍+优缺点比较+示例)

四种生成和解析XML文档的方法详解(介绍+优缺点比较+示例) 众所周知,现在解析XML的方法越来越多,但主流的方法也就四种,即:DOM.SAX.JDOM和DOM4J 下面首先给出这四种方法的jar包下载地址 DOM:在现在的Java JDK里都自带了,在xml-apis.jar包里 SAX:http://sourceforge.net/projects/sax/ JDOM:http://jdom.org/downloads/index.html DOM4J:http://sourceforge.

CUDA C 编程指导(二):CUDA编程模型详解

CUDA编程模型详解 本文以vectorAdd为例,通过描述C在CUDA中的使用(vectorAdd这个例子可以在CUDA sample中找到.)来介绍CUDA编程模型的主要概念.CUDA C的进一步描述可以参考<Programming Interface>. 主要内容包括: 1.Kernels(核函数) 2.Thread Hierarchy(线程结构) 3.Memory Hierarchy(存储结构) 4.Heterogeneous Programming(异构编程) 5.Compute C

Js apply call方法详解

Js apply方法详解 我在一开始看到javascript的函数apply和call时,非常的模糊,看也看不懂,最近在网上看到一些文章对apply方法和call的一些示例,总算是看的有点眉目了,在这里我做如下笔记,希望和大家分享..  如有什么不对的或者说法不明确的地方希望读者多多提一些意见,以便共同提高.. 主要我是要解决一下几个问题: 1.apply和call的区别在哪里 2.什么情况下用apply,什么情况下用call 3.apply的其他巧妙用法(一般在什么情况下可以使用apply)

Js apply 方法 详解

Js apply方法详解 我在一开始看到JavaScript的函数apply和call时,非常的模糊,看也看不懂,最近在网上看到一些文章对apply方法和call的一些示例,总算是看的有点眉目了,在这里我做如下笔记,希望和大家分享..  如有什么不对的或者说法不明确的地方希望读者多多提一些意见,以便共同提高.. 主要我是要解决一下几个问题: 1.        apply和call的区别在哪里 2.        什么情况下用apply,什么情况下用call 3.        apply的其他

js apply()与call()方法详解

摘自此处:http://blog.csdn.net/business122/article/details/8000676 Js apply方法详解 我在一开始看到javascript的函数apply和call时,非常的模糊,看也看不懂,最近在网上看到一些文章对apply方法和call的一些示例,总算是看的有点眉目了,在这里我做如下笔记,希望和大家分享..  如有什么不对的或者说法不明确的地方希望读者多多提一些意见,以便共同提高.. 主要我是要解决一下几个问题: 1.apply和call的区别在