title: 代码之髓读后感2.md
date: 2017-07-08 17:33:11
categories:
tags:


Perl的设计者:Larry Wall在《Programming Perl》中提出,优秀的程序员有三大美德:懒惰,急躁和傲慢。

懒惰是因为为减少总的能量支出而不遗余力的努力。

急躁是因为无法忍受程序执行的低效。

傲慢是因为容不下错误。


语言的诞生

程序设计语言的出现就是为了解决各种各样的问题,而这些问题以现在的语言是无法更合适的处理。

于是乎,语言之于语言,就是目标问题的差异。


语法的诞生

语法是程序设计者制定的规则。因语言而异。

比如运算符的优先顺序,计算流程的规定等等。

对于式子(2+1)*3

书中提到了比较有特点的FORTH语言,书写成:12+3*

即后缀表达式的形式。利用的是栈来实现。

而LISP语言则是前缀表达式:*+123

从语法树的角度来看,连着实际上就是对于相同的树结构进行了不同的遍历方式。

LISP语言语法简单,代码与语法树容易理解并且对应比较直观,此外它还具有宏这样的语法树替换机制,这两个特点催生了结构化编程等一系列现象。

而语言FORTRAN则是引入了运算符优先级和结合性等复杂语法,使得程序员们编写数学表达式更为习惯。编译程序时,它的语法分析器(把原代码作为字符串,读入解析并建立语法树的程序)会将源代码的字符串转换为语法树。

现代语言大多崇尚FORTRAN的语言风格,追求简单便利的编写规则。

但是因为不存在任何解析矛盾的语法体系的设计是十分困难的,又要不断地融入新语法时又要避免与现有的发生冲突,这样更难,正是因此,现代程序设计语言中仍然保留着不少别扭复杂的编写规则。


流程控制

源自结构化程序设计的诞生。

他们的功能原本都可以借助goto来实现。

if... & if...else...

很早以前存在满足条件后跳的命令。

1949年发明的EDSAC就有“特定内存值大于零时跳转”和“特定内存值为负时跳转”这两条命令。

这使得本来要表达 如果满足条件就执行某事的逻辑,不得不变成了 若是满足条件则跳转到某处执行某事

而导入if语句则使得逻辑更为清楚,理解更为直观。

while... & break

主要是用来做那些只要有goto语句就能做的事。带来的附加值是程序的易读性和易写性。

for...

让数值渐增的while更加简洁。for主要通过循环次数来控制循环操作。

升级版的foreach,则根据处理的对象来控制循环操作。

但是并非所有的for语句都可以写成foreach。

这些流程控制语句主要是为了实现程序的 简洁易懂


函数

随着程序的庞大,把握全局愈发困难,同时有可能多次要用到相同的操作。函数因此诞生。把一整块代码切分出来而命名。同时伴随着函数的出现,也产生了递归调用这一编程技巧,非常适合处理嵌套形式的数据。

函数的使用使得程序便于理解和重复使用。

从冗长的程序中切分出反复使用的代码将其封装成一个整体。

遇到问题:一个程序中有几处执行相同的代码若是封装,那怎么返回到原来的地方?

我们希望的是,执行跳转时,记住该位置,之后返回时,又能跳转到该位置后面。

EDSAC的方法是通过修改程序中跳转命令的跳转目的地而实现调用后返回原来的位置。

遇到问题:函数调用者必须知道跳转目的地和返回命令所在地。

创建用来事先记录返回目的地的内存空间,并设计能跳转到该内存空间所记录的地址的命令。

遇到问题:当调用函数X时又调用了Y,返回目的地的内存被覆写,X执行后的返回目的地发生错误。

人们开始使用栈。

这一切都是因为栈这种结构的特性决定的。

由于函数的实际上是代码段的封闭环境,有起始就得有结束,而对于嵌套函数调用而言,必然是越深的,越晚调用的越要及早结束,及早返回,正好符合后入先出的特点。使用栈来实现函数的调用,是极为合适的。

将数据存储的地址依次压入栈中,而栈顶元素内容就是最后被存入数据的地址。

关于递归调用

关键在于如何退出。


错误处理

主要有两种方法:

  1. 使用返回值
  2. 使用异常

使用返回值

通过判断错误的返回值来判断错误并进行相应的处理。

传递错误信息既可以使用返回值,也可以使用事先定义好的全局变量,还可以传递引用形参等方法。

这种方法可以在C语言中见到。

遇到问题:

  • 遗漏错误——忘记做返回值检查;出错条件难以确保完全...
  • 错误处理导致代码可读性下降——为了避免错误导致出现大量错误处理代码,影响可读性...

对于第二个问题,可以在错误处理相同时,使用goto进行集中处理。从代码形式来看,实现了代码和错误处理的分离。

Linus在《Linux内核编码风格》中推荐使用goto语句把函数的结尾处理集中起来。

小栗子:

int main() {
    if(!fun(A)) goto ERROR;
    if(!fun(B)) goto ERROR;
    if(!fun(C)) goto ERROR;
    return 0;
ERROR:
    /* 失败处理 */
}

使用异常(异常处理),出错后跳转

调用函数前设定好错误处理的代码,错误发生时能跳转至相应的错误处理代码。

C之前出现过事先定义好错误发生时跳转的位置,后来演变为了现在的异常处理。

计算机UNIVACI:在计算中出现溢出时,它会执行000处编写的命令。(即 中断(interrupt),键盘按键按下,CPU可以接收信号,传达信息的就是中断)

语言COBOL:只有两种针对性的错误处理。见下例:

READ <文件名> AT END <错误处理语句>
ADD <函数名> ON SIZE ERROR <错误处理语句>

语言PL/I: 引入ON语句。可以自定义增加新的错误类型,也可以主动触发新定义的错误类型。

相较于PL/I的错误处理,现代的JAVA,C++,PYTHON语言的方式有所不同,前者先定义好出错的处理操作,在编写可能出错的代码,而后者则是先用try{...}编写可能出错的代码,在编写出错时的处理。

一些历史

这样的设计的改变源自当初John Goodenough的论文中所提出的方法。

命令有可能会抛出异常,而程序员有可能忘记这种可能性,也可能在不正确的地方编写异常处理或者编写不正确类型的异常处理。为使编译器能够对程序员的错误发出警告,减少这种可能性,需要做到两点。一是明确声明命令可能抛出何种异常,二是需要有将可能出错的操作括起来的语句结构。

这里提议的括起来的语句为基础,现代大部分语言采用了先括起来可能出错的操作,再编写错误处理的语句结构。明确声明命令可能抛出何种异常,这个设计方针在 Java语言的异常检查中得以继承。

1977年语言CLU11引入了异常处理的机制,追加了置于命令后面的错误处理语句结构except。CLU语言从最初就具有用begin…end将代码括成块状的功能,这一功能和except相结合,就实现了将可能出错的操作括起来再补充错误处理的代码编写方式。

1983年C++诞生。针对异常处理的语句结构问题从1984年到1989年间经历了多次讨论,C++语言最终确认追加一种语句结构,把关键字try放在那些被括起来的可能出错代码的前面,把关键字catch放在捕捉并处理错误的代码块前面。按照C++语言设计者斯特劳斯特卢普(Bjarne Stroustrup)的说法,try只是一个为了方便理解的修饰符。 另外还使用throw这一关键字作为触发异常的命令,是因为更易理解的raisesingal两个关键字已经在标准库作为函数名字占用了。

1993年发布的Windows NT 3.1在操作系统和C语言编译器导入了结构化异常处理(Structured Exception Handling,SEH)的概念。结构化异常处理中,除了将可能出错的代码括起来的__try和将错误处理的代码括起来的__except之外,还有将即使出错也要执行的代码括起来的__finally

为什么要引入finally

程序在意料之外结束时,也可以正常的释放锁定的内存和文件等资源,无遗漏的执行成对操作。对于错误处理,要能够不使用返回值检查和goto语句,简洁的实现。

后来Java,Python,Ruby等语言都使用了finally

C++语言中没有finally。C++语言中使用了一种名叫RAII(Resource Acquisition Is Initialization,资源获取即初始化)的技术。比如,在操作打开了就要关闭的文件对象时,定义来操作该对象的类,用构造函数打开,用析构函数关闭。函数结束时,针对函数局部变量,程序可以自动调用析构函数。

2001年出现的D语言以改良C++语言为目标,反对 RAII是优雅的这一意见。打开了就要关闭这样紧密关联的操作,反映在代码上时,如果能放在相近的位置就容易理解多了。基于这一考虑,D语言中引入了作用域守护(scope guard)的概念。通过使用作用域守护,可以事先定义从某一 作用域(如函数)跳出时执行的操作。

何时抛出异常

发生错误应该停止操作立刻报告,这一设计思想被称为错误优先(fail first)。

异常传递

包括Java在内的很多现代语言的异常处理机制中,异常可以传递到调用方。这一设计有一个很大的问题。那就是,即使看到了函数f的代码也不知道函数f可能会抛出什么异常。有可能是函数f调用的另外的函数g中抛出的异常传递过来的,也有可能是函数g调用的函数h抛出的异常。也就是说,如果不看见函数f调用的所有的函数代码,就无从得知函数f抛出何种异常。万一没有察觉到抛出某种异常的可能性,程序就有可能异常终止。

Goodenough主张为了避免这一问题,需要明确地声明可能抛出的异常。Java语言就采用了这一方针。

其他语言中所谓的异常,Java语言中的throw语句也能抛出,并进一步分为三类:

  • 不应该做异常处理的重大问题
  • 可做异常处理的运行时异常
  • 可做异常处理的其他异常

这里的其他异常叫做 检查型异常,如果在方法之外抛出,就需要在定义方法时声明,throws就是为这个目的准备的。

实现异常处理的小栗子:

class Foo {
    // shippai跑出MyException异常
    void shippai() throws MyException {
        throw new MyException();
    }

    // 1. 使用shippai方法声明`throws MyException`
    void foo() throws MyException {
        shippai();
    }
    // 2. 使用catch捕获MyException异常,进行错误处理
    void bar() {
        try {
            shippai();
        }catch(MyException e) {
            ...
        }
    }
}
class MyException extends Exception();

异常处理的问题

一个是当函数有不只一个出口时,必须成对处理的操作很难正确地成对处理。另一个是即便看了代码也不知道函数将抛出何种异常。Java语言的开发者为了解决第二个问题导入了检查型异常,但是这种方法并不太被接受。C#语言的开发者一方面承认检查型异常的优势,另一方面希望有更好的方法出现。

检查型异常的问题:可以说检查型异常是一种非常好的机制。但是这种机制并没有很好地普及到其他语言中。因为它太麻烦。一旦throwstry/catch中异常的数目增多,或者某一方法需要追加一种异常,就不得不修改调用了该方法的所有方法,特别麻烦。

代码之髓读后感——语法&流程&函数&错误处理的更多相关文章

  1. 代码之髓读后感——容器&并发

    容器 单个地址存放单个数据,但是如果有多个数据,而这些数据互相关联,则我们更希望的是将他们能够更好的在内存中组织在一起.于是便出现了容器的概念. 在不同的语言中,容器的名称不同,性质各异.比如,C 语 ...

  2. 代码之髓读后感——类&继承

    面向对象 语言中的用语并不是共通的,在不同语言中,同一个用语的含义可能会有很大差别. C++的设计者本贾尼·斯特劳斯特卢普对类和继承给予了正面肯定,然而,"面向对象"这个词的发明者 ...

  3. 代码之髓读后感——名字&作用域&类型

    名字和作用域 为什么要取名 看着代码中遍地都是的变量,函数,或多或少的我们都应该想过,为什么会有这些名字呢? 我们知道,计算机将数据存储到对应的物理内存中去.我们的操作就是基于数据的.我们需要使用这些 ...

  4. MySQL 储存过程-原理、语法、函数详细说明

    Mysql储存过程是一组为了完成特定功能的SQL语句集,经过编译之后存储在数据库中,当需要使用该组SQL语句时用户只需要通过指定储存过程的名字并给定参数就可以调用执行它了,简而言之就是一组已经写好的命 ...

  5. .NET/C# 中你可以在代码中写多个 Main 函数,然后按需要随时切换

    .NET/C# 程序从 Main 函数开始执行,基本上各种书籍资料都是这么写的.不过,我们可以写多个 Main 函数,然后在项目文件中设置应该选择哪一个 Main 函数. 你可能会觉得这样没有什么用, ...

  6. python基础语法5 函数定义,可变长参数

    函数 1.什么是函数 函数就是一种工具. 可以重复调用 2.为什么要用函数 1.防止代码冗(rong)余 2.代码的可读性差 3.怎么用函数 1.定义函数-->制造工具 2.调用函数--> ...

  7. python基础语法_9-0函数概念

    http://www.runoob.com/python3/python3-function.html 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代 ...

  8. Swift3.0P1 语法指南——函数

    原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...

  9. bootloader启动代码init.s解析----IRQ中断处理函数

    bootloader启动代码init.s解析----IRQ中断处理函数 init.s源代码如下: ;///////////////////////////////////////////// ;opt ...

随机推荐

  1. Spring Cloud(Dalston.SR5)--Zuul 网关

    我们使用 Spring Cloud Netflix 中的 Eureka 实现了服务注册中心以及服务注册与发现:而服务间通过 Ribbon 或 Feign 实现服务的消费以及均衡负载:使用Hystrix ...

  2. Spring生态研习【二】:SpEL(Spring Expression Language)

    1. SpEL功能简介 它是spring生态里面的一个功能强大的描述语言,支在在运行期间对象图里面的数据查询和数据操作.语法和标准的EL一样,但是支持一些额外的功能特性,最显著的就是方法调用以及基本字 ...

  3. sql job定时备份数据库

    ---------------------------------------- 对TestDB1进行备份 ---------------------------------------- decla ...

  4. 被称为“开发者神器”的GitHub,到底该怎么用?

    被称为“开发者神器”的GitHub,到底该怎么用? 原文:https://baijiahao.baidu.com/s?id=1594232691312740966&wfr=spider& ...

  5. 如何修改MSSQL的用户名

    Alter LOGIN sa DISABLE Alter LOGIN sa WITH NAME = [systemAccount] "systemAccount" 为SA的新名称, ...

  6. centos7如何查找文件?

    参考https://blog.csdn.net/allyli0022/article/details/77989664 一.find 根据文件的属性进行查找,如文件名,文件大小,所有者,所属组,是否为 ...

  7. Linux报错:bash: pip: command not found

    $ wget https://bootstrap.pypa.io/get-pip.py$ python get-pip.py$ pip -V #查看pip版本 接下来就可以随便pip安装东西了

  8. 大数据的乘法实现——C语言

    1大数据乘法的算法思路: 输入两个字符串,得到结果,例如:123456789*123456789: 思路:1)首先 123456789*1 = 9   18  27  36  45  54  63   ...

  9. EL(Expression Language)和JSTL标签(JSP Standard Tag Library)

    一.EL表达式: Expression Language提供了在 JSP 脚本编制元素范围外(例如:脚本标签)使用运行时表达式的功能.脚本编制元素是指页面中能够用于在JSP 文件中嵌入 Java代码的 ...

  10. centos 支持安装libsodium

    yum install epel-release -y yum install libsodium -y 然后没了.