写点什么

架构师进阶之《Your Mouse is a Database》

用户头像
陈皓07
关注
发布于: 2021 年 02 月 28 日

Design = Computation + Semantics


我们在讲设计的时候很少去讲设计模式,设计原则这些东西,并不是说没有用。为什么没有重点去讲设计模式和原则呢?

2000 年左右


自己的一些经历:


那时候还没有敏捷的概念。

其实在软件工程比较发达的地区,已经有一些负面的声音,因为商业性太强了。

2000 年的时候,敏捷的概念还没有出来,那个时候有个统一的称呼,是叫轻量级方法。XP 是一个比较先锋的方法,是以技术实践为核心的。主要是围绕编程这件事情。对于小团队来说开发效率提高很多。


XP

2000 左右的时候,XP 和自己想的很一致,感觉就是为程序员量身定制的一样,但那个时候还是个异类。

XP 非常推崇代码质量,kent beck 是 smalltalk 的创始人,面向对象的推崇者。

XP 就把设计模式的潮流推了起来。


个人当时用 C++编程,各种设计模式。当时也是非常痴迷,各种设计模式的书籍。每次会议都提交上百个模式。感觉就不太对,发现永远都学不完。


总结

2003 年的时候,打算把之前学过的东西都用一下。做完之后,把做的东西总结,写了一个书。

《嵌入式电信软件开发》


1.面临挑战,电信领域开发,平台多变,硬件多变,可靠性,升级平滑,快速演进。。。。

2.瀑布式缺点。

3.把结构化方法批评一下。基本都是套话,是不是造成问题的原因,也说不清楚。

4.面向对象。世界观讲起,依赖管理。

要想开发好软件,一定要用敏捷,一定要用面向对象

5.TDD,CI,重构

6.如何向组织引入敏捷开发

7.领域驱动设计

8.各种模式

9.Conduits+框架

10.Active Object 模式

11.主备同步

12.AOP 和横切关系处理


反思

这些东西是不是真的好。假设把电信两个字去掉,却可以应用到其他任何领域。那么既然是这个设计应用于电信领域,为什么可以通用到各个领域。


电信软件到底有哪些特点?


很多根本的问题没有解决,比如说不停机?比如说如何容错?

主备倒换可以做到一些,但是主备倒换是粒度很粗的。

这些核心问题都没有很好的解决方案。


设计模式这个层次上没有办法解决这些问题。


如果说反过来看,哪些是起关键作用的。发现两点起了关键作用

1.问题定义

2.主备保护


Two Papers

<Concurrent functional programming for telecommunications>

<Making reliable distributed systems in the presence of software errors>


看了这两篇论文之后,一切都清楚了。


告诉了你怎么评价一个好的设计。


Pearls of software Engineering


各种方法论:瀑布,XP,敏捷,看板,lean 等

编程语言:上万种语言

设计原则,设计模式:数不胜数


整个软件工程发展至今,反思一下,哪些是真正的精华(珍珠)。

只有编程语言是不可或缺的。其他大部分都是昙花一现。这个东西是确确实实的。是根本性的东西。可以从根本上改变设计的东西。


What does really mean?


我们用 XXX 语言编程是什么意思?


Computation Model + Semantics

用汇编语言,用 C 语言真正的区别是什么?真正的感受是什么?

jmp 和 while 循环是什么区别?汇编语言是在操作物理机器,C 语言看到的虽然也是一个机器,但是层次不一样了。

汇编语言只有全局的,C 语言有局部变量。

本质上就是计算模型和语义不同了。这是决定性的。

这些东西决定了我的开发生产力。


我们面对的问题语义层次更高了。


Rethinking


Semantics

其实程序语言和程序是一回事

假设你用汇编语言编了一段时间,有没有想到发明一个更高层次的语言?


计算的描述和执行分离

x=y+1

这个表达式是个计算的描述,但是在 C 语言里写完之后,我就再也控制不了它了。

计算的描述和执行分离是一个非常强大的解耦技术。

C 语言其实也是个计算的描述。

面向对象变成了世界观,对于世界上所有东西进行建模。这也就是为什么面向对象有那么多新东西出来,其实就是不停的打补丁。


例子:

SQL,makefile


计算模型的区别是根本性的区别。

例子:

界面布局,计量系统,状态迁移表。。。

计量系统

计量系统是啥?如果不能回答的话,只是回答了很多设计模式,设计原则,相当于白做了。只能用对象模型上面去解释。


面向对象只是更好的汇编语言


计量系统的本质就是个向量计算。


状态迁移表是一个模型。是一个状态机的抽象。然后有个解释器解释这个表。


线性代数

向量和矩阵只是语法,背后是计算模型。线性代数也是个 DSL。


总结

面向对象是很好的实现工具,但不是唯一的建模工具。


Design


核心步骤

Problem Domain -> Requirements -> Computation Model + Semantics

-> Languages(API/Data representation) -> Interpreter/Compiler


这条路可以走很远,可以一直走下去。


  • 设计模式。面向对象只是一种特定的计算模型。设计模式大部分情况下都是补丁

  • DDD。像 DDD 的思想很早之前就已经很流行了,《DDD》这本书就很难懂,依附于面向对象,把问题搞复杂了。核心就是划分不同的子领域,应用不同的计算模型。

  • DCI。是 06 年提出来了,职责分派。系统的整体的行为看不见,DCI 就是解决这个问题的,就是解决面向对象的缺陷。核心思想把对象的角色(行为)和数据进行分离,Context 就是场景,纯的函数就是描述我的计算。这样我就能看到一个核心的视图了。数据的继承和方法的继承性质完全不一样,如果放在一起就会出问题。


字符串解析领域用 C 语言也可以写,但是抽象出来正则表达式就不一样了。

如果是特定领域,只要把核心的东西拿出来,定义好数据表达,就已经很好了。


总结


什么是编程:

不过是在某种计算模型的基础上表达计算。


什么是程序:

程序是一个特定的机器,这个机器可以用通用机器来模拟。


CASE: POTS loop


电话系统。

现在比较流行的是 Reactive Programming。有个 Reactive 联盟。今年 armstrong:Reactive 系统其实早就有了,就是交换机。Reactive 里面所有的概念都是通信领域的一些概念。


Distrubute Networks Server

我们希望事件是有序的,但是实际上来的时候是无序的。


Semantics Gap:

Order vs unorder

Determinism vs Non-Determinism

Thread(顺序型的 sequential) Vs Event(Callback)


方式 1:Block 串行调用

可以,但是不可用的。吞吐量太差。

每个用户一个线程,太消耗资源。


方式 2:Non-Block

状态机变复杂了。


电信领域

电信领域 10 大问题


需要的语义

封装语义

并发

错误检测

位置透明

动态代码升级


基于这样的语义解决


编程问题 Count Down

思路

给我一个问题,我先把这个问题说清楚。

先不考虑性能,一定对的方法,把问题描述清楚。


总结:先形式化一个答案。


问题:什么才是一个正确的解?

先解决你给我一个解我告诉你是否是正确的解。


这个做完之后可以进行程序证明。


Your Mouse is a Database


基础是并发。

是面向并发编程的一个概念在里面。

ACM 的一篇论文,是一个库的核心思想的展现。


UI 的编程模型虽然经过几代的变换:

1.loop 循环,加 listener

2.事件回调,OnMouseDown, OnMouseUp

所以鼠标也是产生数据的东西,和数据库没什么区别,鼠标也是产生数据的源。

鼠标一点数据就出来了,在数据层面上没有区别,在 DB 可以用 SQL 来写。那 OnMouseDrag 怎么写呢?在原子操作里是没有 OnMouseDrag 的原子的。


状态机模式

通用方法:

OnMouseDown 之后,置一个标志,OnMouseMove 就开始是 OnMouseDrag 了,OnMouseUp 之后再把标志回位。

其实就是个状态机


select 模式

select * from xxx

不然整个逻辑都散落在钩子函数里面去。

我们能不能像 SQL 语句那样对鼠标进行编程。


典型应用


  • 流式

  • 无限

  • 实时

异步实时消息推送的场景

比如,订阅一个话题,有消息推送给我。

如何对这样的场景进行编程呢?如果用回调的方式编程,业务的复杂性非常高的。


实例:单次补齐


场景:用户输入太快,大于 200ms 才有效。否则会有重复。


数据正方体。

解决低延时的数据流。


Callback Model

  • return in one shot

  • 回调都是返回 void,回调函数是无法通信的,只能通过全局变量

  • Non-first class value


1.先解决 void 的问题:

现有个返回值出来,以前是 void,现在需要一个返回结果,后面可以操控的东西。

Result

  • close

  • get data


这个叫做 Future,并发编程里都有这个概念。


2.如何把 Future 变成非 Block

Block 和 pull 是一个事情,主要是数据来的时候我不知道。

解决方法,挂接回调函数。


异步消息特点:

不定期,可能是源源不断的。这样推演下来我们把整个异步流当成一件事情


stream behaviour


tuple module 语法


Code Now


-module(stream).-compile(export_all).
-type stream()::{stream, pid()}.
-spec start()->stream().
start() ->
    Pid = spawn(?MODULE, loop, [[]]),
    {stream, Pid}.
subscribe(Ob, {stream, Pid}) ->
    Pid ! {subscribe, Ob}, 
    ok.
loop(State) ->
    receive
        {subscribe, Ob} ->
            loop(Ob);
        Msg ->
            Ob = State,
            Ob(Msg),
            loop(State)
    end.
    
cast(Msg, {stream, Pid}) ->
    Pid ! Msg.
where(Pred, Stream) ->
    WS = stream:start(),
    Stream:subscribe(
        fun(D) ->
            case Pred(D) of
                true ->
                    WS:cast(D);
                _ ->
                    pass
            end
        end),
    WS.
select(F, Stream) ->
    WS = stream:start(),
    Stream:subscribe(
        fun(D) ->
            WS:cast(F(D))
        end),
    WS.
select_many(Inflator, Stream) ->
    SMS = stream:start(),
    Stream:subscribe(
        fun(A) ->
            (Inflator(A)):subscribe(
                fun(B) ->
                    SMS:cast(B)
                end
            )
        end),
    SMS.
test() ->
    S = stream:start(),
    (S:where(fun(D) -> is_integer(D) end))
      :subscribe(fun(Msg) -> io:format("Msg ~p~n",[Msg]) end),
    S:cast("Hello"),
    S:cast(1).
test2() ->
    S = stream:start(),
    ((S:where(fun(D) -> is_integer(D) end))
       :select(fun(D) -> 2*D end))
       :subscribe(fun(Msg) -> io:format("Msg ~p~n",[Msg]) end),
    S:cast("Hello"),
    S:cast(18).
mouse_down_stream() ->
    stream:start().
mouse_move_stream() ->
    stream:start().
test3() ->
    MouseDown = mouse_down_stream(),
    MouseMove = mouse_down_stream(),
    (MouseDown:select_many(fun(_D) -> MouseMove end))
              :subscribe(fun(Msg) -> 
                             io:format("mouse drag ~p~n",[Msg]) 
                         end),
    MouseDown:cast(down),
    MouseMove:cast({10,10}).  
复制代码


      

模式小总结


这种方式用面向对象的方法来解决永远解决不了的。这是计算模型的胜利。

从语义出发。


iterater 和 oberserver


在数学上是对偶的。iterator 也是个流。一个是 pull,一个是 push。

《Gof》那本书可以扔掉?


解决 mouse up 的问题


刚才只实现了一半。现在要解决 up 的问题。


结束条件

mouse drag 是有条件的 mouse move 流,从语义的角度来看。我们还需要什么语义?


mouse drag = mouse move between (mouse down, mouse up)

所以我们需要实现一个 between(s1,s2) ->form s1 ...until s2


组合

组合是一个非常重要的性质,大的程序可以通过小的程序组合而来。until 应该是比 between 更基础的原子。


code

until(ES, S) ->    US = stream:start(),    Ob1 = fun(E) -> US:cast(E) end,    Ob2 = fun(E) ->              S:unsubscribe(Ob1),             US:stop()           end,    S:subscribe(Ob1),    ES:subscribe(Ob2),    US.
复制代码


有什么问题?

ES:subscribe(Ob2)
复制代码

没有 unsubscribe,在哪个时机 unsubscribe?

until(ES, S) ->    US = stream:start(),    Ob1 = fun(E) -> US:cast(E) end,    Ob2 = fun(E) ->              S:unsubscribe(Ob1),             US:stop(),             ES:unsubscribe(Ob2)          end,    S:subscribe(Ob1),    ES:subscribe(Ob2),    US.
复制代码


ES:unsubscribe(Ob2)
复制代码

编译不过。


自己引用自己没有办法,就要寻求外面的力量,可以改一下 stream 的实现


扩展 stream 协议


应用 erlang 的 behaviour


并发


电信领域,并发是最本质的特性。


用户头像

陈皓07

关注

还未添加个人签名 2019.04.11 加入

还未添加个人简介

评论

发布
暂无评论
架构师进阶之《Your Mouse is a Database》