// 上一篇:管道(pipeline)

// 下一篇:程序计数器(PC)


“编程语言不过是一个工具,什么语言都一样”,“编程语言能改变人的思维,不同的语言会带给你不同的思考方式”,这是我学习编程之后经常听到的两种说法。到底哪一种更靠谱呢?这要看说者从什么角度去考虑的。

同样是“编程语言不过是一个工具,什么语言都一样”这句话,不同的人所指的也不一样。有的人用什么语言都只写基本都CRUD代码,那他可能会觉的好像换个语言也没什么差别,所以会觉的用什么语言都一样。有的人并不怎么关心技术内功,只是完成工作,早早有机会就转去做非技术工作,他可能也会觉的编程语言不过是一个工具,对他来说没什么差别。有的人则是用过各种语言,做过各种项目,深刻理解到了不同语言共性的地方,例如在解决很多领域问题的时候,语言的实现上是一回事,但怎样对领域特定问题建模,构架可能是更重要的,实现上的差异此时可能不起决定作用,他可能会说编程语言是个工具。诸如此类,不一而足。

而持“编程语言能改变人的思维,不同的语言会带给你不同的思考方式”的人,也大致有分类。有的人并没有深入理解日常使用的一个编程语言,频繁辗转于学习各种新新语言,每个语言都写写HelloWord,可能觉的不同的语言其实没什么差别,或者好像某个语言只是多了一些不同的关键字,少了一些语言特性等等。这样的时候,会迷失在不同语言的表象之下。而有的人深入理解了日常使用的语言,并用它开发了实际有用的软件、项目、产品等,包括业务系统和核心系统,完整的工程实践让他理解到了很多语言特性的优劣,这些优劣不仅仅局限在“哇,这个语言太牛了,如此的自由表达”这样的“做加法”的理解上,而是包括了“嗯,这个语言的这几个设计让程序开发在整体上倾向于模块化、可维护、避免复杂细节”,以及“嗯,这个语言的这些特性要限制性使用,它们会带来一些状态爆炸以及难以维护的意大利面条”,“这个语言在错误处理上提供了不错的机制,这个机制可以更快发现问题而不是隐藏问题”,等等这样的思考。这样的经过工程实践的理解是另外一种。也有人说不同的编程语言提供了不同的范式:“面向过程、面向对象、函数式”等等,会写function不等于面向过程就学好了,会写class也不等于面向对象就学好了,会写lambda不代表对函数式里的无状态假设就理解到位了。

比如“全局变量是代码不可维护的根源”这点,在不同语言里可能有不同形式的存在:

  • 直接使用全局变量+多个全局函数,这些全局函数直接访问全局变量。类似这样:
int g_xxx;
float g_yyy; void do_something1(){
g_xxx = ...;
} void do_something2(){
g_yyy = gxxx + ...;
} ...

也许一开始代码很少,可以这样写,但是当项目演化、需求变更,全局变量就各种跳线,会形成意大利面条代码,难以维护。

  • 有人说,那我全部装进一个class总可以了吧?此时要避免“万能类”,类似这样:
class Manager{
int g_xxx;
float g_yyy; void do_something1(){
g_xxx = ...;
} void do_something2(){
g_yyy = gxxx + ...;
} //...
void do_something100(){
g_yyy = gxxx + ...;
}
}

在一个class里面塞进了太多的功能,也不分模块,一样是犯了“全局变量”的毛病。即使是有经验的程序员,在面对项目演化,需求变更,BUG修改的时候,也可能会把本来状态简单的一个类改着改着变成一个万能类。因此,在这个过程中要写出真正的只干该干的事情的Object,而不是挂羊头卖狗肉的函数和数据的集合。

  • 换一个带有闭包的语言,闭包带来了便利,使得我们不必写非常多的辅助函数和辅助类就可以在一个闭包里完成一堆功能。类似这样:
function dosomething(onDone)

	local conext = {
state = ..,
data = ...
} local function do_something1(onComplete)
context.data = ...
end local function do_something2(onComplete)
context.state = ...
end local function do_something2_rec(onComplete)
....
do_something1(function()
do_something2_rec(function()
....
end)
end)
end ..... do_something2_rec(function()
onDone()
end) end

在这里,一个闭包里做了递归之后,实际上这些闭包函数+context,构成了一组本来应该一个class做的事情,这样的代码如果不是逻辑清晰和直观的,久而久之就会变的难以维护。一个闭包隐约等价于一个class等构造函数,如果说在class的构造里,我们要注意避免资源的循环引用,那么在闭包里同样是需要的。例如:

function connect()
local connection = createConnection() connection:OnStateChange(function(conn,oldState,newState)
connection.xxx = ...
end)
end

这里就在OnStateChange里闭包里connection对象自身,只要这个闭包还存活着,connection就不会死掉。所以需要小心的在必要的地方移除注册的闭包。另外一种就是在OnStateChange里不去闭包外部的connection,而是使用函数本身的参数conn。

实际上,当闭包变的复杂,我们可以通过将其重新封装成一个对象来让代码模块化和可维护:

function dosomething(onDone)
let obj = Context:New(...)
obj.run(function()
onDone()
end)
end

重新来看“全局变量”,应该是这样的角度去理解:

全局变量+多个全局函数,等价于在函数内的闭包变量+多个嵌套函数,等价于在写在一个类内部的多个应该被拆分的逻辑子类,都是耦合和不可维护的根源。

而如果从函数式语言的角度来看,函数式语言里提供lambda反而没有这么多毛病,因为函数式语言是无状态的,所以闭包并不会因为状态的组合导致副作用。

从而,我们可以回过头理解下关于函数的“自由变量”:

function test(a,b)
return a+b+d;
end

a和b都是test函数的约束变量(bound variable),而d是test函数的自由变量(free variable)。无论这个函数是在全局的,calss里的、局部的。自由变量导致函数不可重入,有副作用,所以在非函数式语言里写局部函数、匿名函数都应该注意减少自由变量,减少函数的副作用。从这个角度来说,一个闭包可以看成是功能比较强的函数,同时也是一个功能比较弱(模块化,封装性差)的class。

控制结构(8): 线性化(linearization)的更多相关文章

  1. 控制结构(8) 线性化(linearization)

    // 上一篇:管道(pipeline) // 下一篇:程序计数器(PC) "编程语言不过是一个工具,什么语言都一样","编程语言能改变人的思维,不同的语言会带给你不同的思 ...

  2. 控制结构(7) 程序计数器(PC)

    // 上一篇:最近最少使用(LRU) // 下一篇:线性化(linearization) 程序的每一行都是一个状态,对应的行指令.同步的情况下同一个pc一直自增,异步的时候,分裂出一个新的子pc,独立 ...

  3. Scala入门3(特质线性化)

    尝试设计一套特质,灵活的改动整数队列.队列有两种操作:put把整数放入队列,get从尾部取出它们.队列是先进先出的,get应该依照入队列的顺序取数据.提示:可以用mutable.ArrayBuffer ...

  4. feilong's blog | 目录

    每次把新博客的链接分享到技术群里,我常常会附带一句:蚂蚁搬家.事实上也确实如此,坚持1篇1篇的把自己做过.思考过.阅读过.使用过的技术和教育相关的知识.方法.随笔.索引记录下来,并持续去改进它们,希望 ...

  5. Python’s super() considered super!

    如果你没有被Python的super()惊愕过,那么要么是你不了解它的威力,要么就是你不知道如何高效地使用它. 有许多介绍super()的文章,这一篇与其它文章的不同之处在于: 提供了实例 阐述了它的 ...

  6. 控制结构(9) 管道(pipeline)

    // 上一篇:线性化(linearization) // 下一篇:指令序列(opcode) 最近阅读了酷壳上的一篇深度好文:LINUX PID 1 和 SYSTEMD.这篇文章介绍了systemd干掉 ...

  7. 控制结构(9): 管道(pipeline)

    // 上一篇:线性化(linearization) // 下一篇:指令序列(opcode) 最近阅读了酷壳上的一篇深度好文:LINUX PID 1 和 SYSTEMD.这篇文章介绍了systemd干掉 ...

  8. 控制结构(2) 卫语句(guard clause)

    // 上一篇:分枝/叶子(branch/leaf) // 下一篇:状态机(state machine) 基于语言提供的基本控制结构,更好地组织和表达程序,需要良好的控制结构. 典型代码: 同步版本 f ...

  9. 控制结构(2): 卫语句(guard clause)

    // 上一篇:分枝/叶子(branch/leaf) // 下一篇:状态机(state machine) 基于语言提供的基本控制结构,更好地组织和表达程序,需要良好的控制结构. 典型代码: 同步版本 f ...

随机推荐

  1. 1.docker常用命令

    1.启动交互式容器 $ docker run -i -t IMAGE /bin/bash -i --interactive=true|false 默认是false -t --tty=true|fals ...

  2. mysql查找字段在哪个表中

    select table_schema 数据库名称,table_name  表名 from information_schema.columns where column_name = 'compar ...

  3. mybatis基础(上)

    框架图 SqlSessionFactoryBuilder 通过SqlSessionFactoryBuilder创建会话工厂SqlSessionFactory 将SqlSessionFactoryBui ...

  4. java泛型-泛型类,泛型接口,常用形式

    泛型简单使用: package com.etc; import java.util.ArrayList; import java.util.List; /* 泛型就相当于<>一个标签,泛化 ...

  5. audio currentTime 时间戳转换(es6语法)

    function format(interval){ if (!value) return '' let interval = Math.floor(value) let minute = (Math ...

  6. 启用WCF压缩提升Dynamics 365 CE的网络性能

    摘要: 微软动态CRM专家罗勇 ,回复307或者20190308可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!我的网站是 www.luoyong.me . 本文系根据微 ...

  7. 开源项目商业分析实例(1) - MonicaHQ

    本来写一篇开源商业模式的稿子,因为有四大主题,这个稿子有点大,导致现在半个月过去了,都还没有憋出来.   今天想想还是采用MVP(minimum viable product,最小化可行产品)模式吧. ...

  8. 从.Net到Java学习第七篇——SpringBoot Redis 缓存穿透

    从.Net到Java学习系列目录 场景描述:我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回.这个时候如果我们查询的某一个数 ...

  9. C# 发送电子邮件源码片段

    下面代码内容是关于C# 发送电子邮件片段的代码,应该对各位有所用途. using System;using System.Web;using System.Web.Mail;public class ...

  10. redis 五大数据结构__常用命令

    linux 下下载redis数据库 apt install redis 如果提示权限不够的话, 直接提权: sudo apt install redis-server linux启用.停止服务 ser ...