从面向对象编程看分解和综合,个体和整体

计算机描述世界和解决问题的方式是对象、算法和编程(算法用语言表示出来)。计算机科学的对象是计算机科学在描述世界的时候对所描述的对象的抽象。一般来说,一个对象对于这个对象的使用者来说,主要关注的是:这个对象和外界是什么关系,也就是外界输入给这个对象什么,这个对象给外界输出什么;同时,刻画这个对象的编程者,关注的是这个对象内部需要什么元素,元素之间什么关系,这些内部元素和元素之间的关系如何按照接收到的外界信息来更新状态,从而如何产生输出给外界的符合其行为的输出。也就是说,对于把这个对象和程序当做工具来使用的人来说,我们得看到这个对象的整体,也就是跳出来看;对于设计和实现这个对象和程序的人来说,我们得看到这个对象的细节以及细节和整体的联系,也就是钻进去看。

如果我们有的时候需要创造性地使用这个工具,那仅仅跳出来就不够了。因为不了解细节如何实现的人,只能把这个工具用到它所设计的地方。就好像说,一个扳手就是用来拧螺丝的。但是,如果我们还了解这个扳手的其他细节,例如比较重,例如比较硬还有一定长度,没准在需要的时候我们还可以把扳手当做榔头或者撬棍来使用。同样,如果我们了解这个对象和程序的一定的细节,有需要的时候,我们就可以把这个程序的一部分算法和程序甚至设计上的想法拿来在适当的场合重用,或者迁移到一个完全不同的场景。这也就是创造性地使用工具和创造工具。

因此,如果要创造知识和创造性地使用知识,我们在学习的时候就要进得去处得来,跳出来的时候看到整体忽略其内部细节,钻进去的时候看到细节以及细节和整体的联系,合起来就是从细节看到整体,从整体的角度来看细节。这就是我们说的分解和综合,或者说既见树木又见森林,或者说,系联性思维。

Overleaf支持Markdown和LaTeX混合写作

对于稍微篇幅长一点的或者经常有公式和参考文献这种编号会变化的内容的写作来说,仅仅使用纯文本(text)的标记语言远远比隐藏了大量格式说明的“所见即所得”的文字编辑器方便。

当然,最成功和万能的是\(\LaTeX\),mediaWiki也很好,HTML也不错,Markdown语法更简单(注意,就算后面三者,实现数学公式的时候还是靠一个叫做MathJax的依赖\(\LaTeX\)语法的软件)。很早以前的WPS其实也是标记语言,后来就跟堕落的Word学习了这种——适用于不想深入一点学习结构化写作和一点点语法的用户,适用于写纯文字的短文章的用户的——所见即所得。结构化写作,先布局整体结构,再来补充细节,是能够启发你的思考的写作方式。任何人都应该优先尝试一下结构化写作,而不是顺着思路往下写。

对于不需要数学公式,但是还是需要参考文献以及结构化写作的用户来说,\(\LaTeX\)可能学习成本太高,也不太有必要。那是不是可以用Markdown来写书呢?这是可以的,例如“简书”和”Gitbook”就是一个可以用Markdown写书的网站。但是,对于一个团队,可能其中一部分成员已经习惯了用\(\LaTeX\),另一部分习惯用Markdown。怎么办呢?

当然,很早就有人来做三者之间的转换,例如pandoc。不过,很少有软件,并且是在Web端的不需要安装到本地的软件,能够让你直接把其中的两种甚至三种混合起来使用。Web端的软件第一是不用安装维护,第二是方便多人协作,如果还能提供版本管理功能就更好了。

现在,Web端\(\LaTeX\)写作平台Overleaf,就提供了这样一个实现(当然还有待于进一步提高,例如,除了PDF,如果最终能够输出成markdown或者\(\LaTeX\)格式就更好了):通过使用markdown.sty宏包,可以在\(\LaTeX\)中同时使用\(\LaTeX\)和markdown。具体可以参考这里的模板:
https://www.overleaf.com/learn/latex/Articles/How_to_write_in_Markdown_on_Overleaf
https://www.overleaf.com/learn/latex/Articles/Markdown_into_LaTeX_with_Style

R和R包的安装

R的最新版需要专门去CRAN镜像下载安装文件。Ubuntu库里面的R通常不是最新版。如果要重新安装,则最好清空之前安装的软件包。例如删除掉“/usr/local/lib/R/site-library”之类的。然后重新安装R。

完成之后,后来安装的很多包,例如”devtools”, “EpiEstim”可能依赖Ubuntu的编译功能和Ubuntu系统的软件包。因此,最好在Ubuntu安装一下编译包,例如运行一下“sudo apt install build-essential libcurl4-gnutls-dev libxml2-dev libssl-dev”之类的。再继续安装一下lapack之类的软件包。根据R里面安装的提示,还需要去下载相应的包。这个R包管理上和Ubuntu系统包没有整合在一起,比较麻烦。只好不断地发现错误,不断地补充包。

其中有一个软件包源程序(stringi/icudt61l.zip)很难获得,需要单独去下载回来(http://www.ibspan.waw.pl/~gagolews/stringi/icudt61l.zip),然后本地安装。或者到网络好的地方多安装几遍。

本地安装方法,例如:install.packages(“~/dplyr-master.zip”, repos=NULL, type=”source”),或者 install.packages(“~/EpiEstim3_src/EpiEstim”, repos=NULL, type=”source”)

diff, latexdiff, diff-pdf和diffpdf

由于有的时候需要在不同的电脑上编辑latex文件,可能会由于偶尔没有保持同步产生版本冲突,也不一定会记得哪一个是最终版本。这个时候就需要通过对比latex或者产生的pdf文件来明确保持哪一个版本或者融合多个版本。为了这个目的,有几个工具,diff, latexdiff, diffpdf和diff-pdf。其中前者直接检验tex文件的文本差异,latexdiff也直接工作在tex文件上但是仅仅比较实际上看到的编译后文件的不同(通过调控参数,也可以直接比较文本),diffpdf和diff-pdf对比两个pdf文件找到不同。

不过在实际使用中,要注意,如果两个文件相差特别大,则比较出来的结果通常没有意义。更好的比较算法应该是识别出来最相似的对应模块以后,在这个基础上比较不同。这就需要修改这些diff的算法了。

从编程序看分析和综合,以及用于写作和做报告

最近,不得不来指导一下学生编程序。除了

  1. 给程序加执行参数(例如linux命令cp后面的文件名就是用来满足适于用任何文件的要求,例如ls后面的-a就是用来显示隐藏文件等等)来增加程序的功能而不是完成非常特定的功能,然后每次不得不去修改程序本身
  2. 运行参数包含测试功能的开关,这样测试起来简单
  3. 程序尽量要做到命令行下可执行,不要依赖于集成环境IDE,可以用makefile
  4. 变量名要有意义
  5. 程序要加上说明,看的出来每一行在做什么
  6. 合适的时候,做一下优化,例如用矩阵代替循环之类的
  7. 一般的编程,以及每一种语言,基本上都会有一些风格习惯,要稍微了解和遵循

这些基本的要求之外,最重要的是Divide and conquer的思想,也就是分解和综合的思想:把每一个程序分解成合适的步骤,每一步完成特定的功能。甚至在主程序这样的高等级程序里面,完全不应该有具体的计算,只能有每一段应该完成什么的流程;甚至在比较高级的功能性模块里面,也只能由每一段应该完成什么的流程;仅仅在最底层的程序里面才应该有具体计算,核心计算。也就是程序应该长成这样:

main{
   get some run-time parameters #meaning of every parameter
   to do task1 #what is this task, why it is needed here
   to do task2 #what is this task, why it is needed here
   to do task3 #what is this task, why it is needed here
   testcode #activated when the designated run-time parameter is called
   print results #to serve what purpose
}


task1(parameters){
   to do task11 #what is this task, why it is needed here
   to do task12 #what is this task, why it is needed here
   testcode #activated when the designated run-time parameter is called
   return something back to the calling program
}


task11(parameters){
   Realcode, core code #how this task is done
   testcode #activated when the designated run-time parameter is called
   return something back to the calling program
}

这样的程序,尽管初看起来,会云里雾里,发现,每个子程序都没有在干活啊,就是在调用下一级子程序,好像只有搞懂最底层的子程序,才能看懂这个上一级程序。但是,只要稍微习惯一下,改变一下思考问题的方式,就会发现,这样的程序比把核心程序和流程程序放在一起的,要好懂的多好懂的多。关键的思考问题的方式就是分析和综合,或者叫做Divide and conquer,或者WHWM分析方法(What,How,Why,Meaningful)。具体来说,就是,在看上一级程序例如主程序的时候,每一个子程序只要明白在完成什么功能,这个功能和当前程序的目的是什么关系,就行了。不需要取搞清楚子程序是怎么做的。也就是说,What(完成什么功能)是最主要的,然后是Meaningful(起到什么作用),How(怎么做的)和Why(为什么这样做,为什么做这个)是次要的。只有当真的需要的时候才进入下一级子程序,而且就算进入下一级子程序,还是一样,先搞清楚这个子程序下面的流程,也就是每个子子程序的功能以及它们和这个子程序的功能的关系,而不去管这些子子程序是怎么实现的,有没有更好的实现。当然,这样的阅读依赖于程序员的注释,因此,每一个功能模块,都要做好注释,完成什么功能。

记住:很多细节我们都可以不懂,只要我们懂得这个东西做了什么,在整体中起到什么作用。当然,如果我们真的想搞懂整个问题,那么,自然,底下的How和Why也是重要的。

这样来做还有什么好处呢?方便测试,方便协作。测试的时候,可以单独来针对特定的子程序,而且可以设定一个针对这个子程序具有精确解的情况来测试。协作的时候,每一个贡献者可以只管当前任务的细节,而不去管这个任务之上的子程序和之下的整合,当然,在把任务分解准确以后。

只要做好分解,不断地分解——每一次都只有流程直到不得不写下来核心程序,做好注释,做好测试,无论编程水平(例如语言包的熟悉程度、算法的创造性运用)本身多烂,都能够编出来好程序。

同样,写作和阅读的时候,也是如此:首先是整篇文章想说什么主要信息,为什么说这个;接着把整篇文章分解成一个一个的部分,每一部分说了什么主要信息和整篇文章的关系是什么;再接着,还可以把每一个部分再次分成一个一个的部分,每一步分说什么主要信息和它的上一层关系是什么。也就是在每一个层次上,问What和Meaningful的问题,然后依靠在下一层问What和Meaningful的问题来回答上一个层次的How和Why。

同样,做学术报告的时候,更加要如此:首先是整个报告想说什么,为什么说这个;接着,用什么例子或者结论来展开论证和表达这个主要信息,以及反过来,在介绍这些例子和结论的技术细节的之前一定要让你的听众明白,你为什么将这些例子和结论,它们和主要信息的关系是什么;然后,对每一个例子和结论,做同样的事情,先搞清楚需要用哪些进一步的细节来展开这些例子和结论,在具体进入这些进一步的细节的具体技术细节之前,能不能交代清楚这些细节和这些例子和结论的主要信息是什么关系。不断地重复这个分解的过程、明白起到对上一级的内容什么样的支撑作用、然后才是细节的展开。为什么要这样做?因为你的听众基本上不会一下就明白最底层的细节,每一次的封装都能够降低你的听众的记忆和信息处理负担。

当然,有的时候,这个层次会有交叉。这也是为什么要依靠概念地图——整体是层次结构,但是跨越层次的联系是最重要和核心的联系。这也是系统科学,物理学,或者说科学的核心思想之一——分析和综合。除了学习物理学、系统科学,你还可以通过练习编程来体会这个分析和综合,只要完成一个需要300行以上的程序,你就不得不体会到。任何语言都可以,但是,不要直接就是图形界面的例如VB之类的,试试C、Python、Java等,尤其是Java(接口,而不是具体程序)。