zoukankan      html  css  js  c++  java
  • 关于系统程序员的一些感悟

    不知是什么时候在网上找到一篇文章,说实话,确实不错。至于作者之类的,在网络这个神奇的地方,也无从考证了。也就贴一些写得比较好的地方吧。

    软件开发的困难在哪里?对于这个问题,不同的人有不同的答案,同一个人在不同职业阶段也会有不同的答案。作为一个系统程序员来说,我认为软件开发有两大难点:

    一是控制软件的复杂度。

    软件的复杂度越来越高,而人类的智力基本保持不变,如何以有限的智力去控制无限膨胀的复杂度?我经历过几个大型项目,也分析 过不少现有的开源软件,我得出一个结论:没有单个难题和技术细节是我们无法搞定的,而所有这些问题出现在一个项目中时,其呈指数增长的复杂度往往让我们束手无策

    二是隔离变化。

    用户需求在变化,应用环境在变化,新技术不断涌现,所有这些都要求软件开发能够射中移动的目标。即使是开发基础平台软件,在超过几年 时间的开发周期之后,需求的变化也是相当惊人的。需求变化并不可怕,关键在于变化对系统的影响,如果牵一发而动全身,一点小小的变化可能对系统造成致命的影响。

    为了解决这两个问题,方法学家们几十年来不断努力,他们发明或改进软件的开发过程和设计方法。系统程序员面对的基础软件通常都是大型的复杂的软件,其通用性也要求能容纳更多变化,解决这两个问题也是系统程序员成长计划的主要目标。

    温伯格说过,医生的药方包括药物和服药的方法,两者缺一不可。同样的教材,不同的学习方法,效果也有很大差别。

    • 操作系统使Linux。

    Linux是最适合程序员使用的操作系统,它是开源的,有多种不同的发行版可以免费使用,这些发行版默认安装就带了开发工具。学习Linux本身就需要一本书,如果你从来没接触过Linux,也不用惊慌,花几个小时学会十来个常用的命令就够了,其它的以后慢慢再学。

    • 编辑器使用VIM。

    编辑器的功能是创建源文件,也就是把我们编写的代码输入到电脑中。vim和emacs是Linux下最流行的 代码编辑器,vim入门更简单,功能也很强大。它支持查找剪切替换等基本编辑功能,也支持符号跳转和代码补全等高级编辑特性。vimtutor是最好的入 门教材,初学者跟着这个tutor学习一遍就可以用它来编程了,等用得比较熟练之后,再去掌握那些高级功能。你掌握得越熟练,你就能更高效的工作,这个投 资是值得的。

    • 编译器使用gcc。

    编译器的功能是把源代码翻译成计算机可以“读懂”的机器语言。在Linux下可用的C编译器有好几个,gcc 是其中最流行的,大多数发行版都默认安装了gcc。gcc的参数很多,看起来很复杂,我们只掌握最简单的用法就好了,大概像这样的:
    gcc -g test.c -o test。

    • 调试器使用gdb。


    调试器的功能是帮助程序员定位错误,这是最后一招,也是最不期望的一
    招,使用调试器越多通常说明你的水平越 差,不过对初学者来说,掌握这个工具必不要可少的。gdb的功能强大,推荐读者使用命令行的gdb,它更灵活更方便。读者先掌握如何设
    置断点、显示变量和 继续执行等基本操作就行了。

    • 工程管理使用make。

    make是Linux下最流行的工程管理工具,Makefile是make的输入文件,它本身就相当于一 种编程语言,执行make相当于调用其中的函数。编写Makefile是一件繁琐无趣的工作,幸好我们不用学习它,后面我们会讲解make的改进版 automake,现在你能写出下面这种简单的Makefile就行了:
    all:
    gcc -g test.c -o test
    clean:
    rm -f test
    在这里,你可以把all看作一个函数名,gcc -g test.c -o test是函数体(前面加tab),它的功能是编译test.c成test,在命令行运行make all就相当于调用这个函数。clean是另外一个函数,它的功能是删除test。如果你有时间学习一下Makefile当然更好,如果没有时间,了解这么多也够了。

    需求简述:
    或许你还在欣赏用良好代码风格重新编写的双向链表,看起来不错,不是吗?不过这还远远不够,专业程序员要有精益求精的精神。至于要精到什么程度,与 具体需求有关,如果只是写个小程序验证一下某个想法,那完成需要的功能就行了,如果是开发一个基础程序库,那就要考虑更多了。侯捷先生说过,学从难处学, 用从易处用。这里我们是学习,就要精
    得不能再精为止,精到钻牛角尖为止。在后面的几章中,我们将对这个双向链表进行持续的重构,这个过程是循序渐近的,请 读者不要着急,稳扎稳打的学习是才最好的。在这一节
    中,我们要学习的是程序的封装性,请读者思考下面几个问题:
    1. 什么是封装?
    2. 为什么要封装?
    3. 如何实现封装?
    花上半小时去思考,尝试回答上述问题,然后按你的想法重写双向链表。

    1.什么封装?
    人有隐私,程序也有隐私。有隐私不是什么坏事,没有隐私人就不是人了,程序也不成其为程序了。问题是隐私不应该让别人知道,否则伤害的不仅仅是自 己,相关人物也会跟着倒
    霉,“艳照门”就是个典型的例子。程序隐私的暴露,造成的伤害不一定有“艳照门”大,也不一定比它小,反正不要小看它就行了。封装 就是要保护好程序的隐私,不该让调用者
    知道的事,就坚决不要暴露出来。
    2.为什么要封装?
    总体来说,封装主要有以下两大好处(具体影响后面再说):
    隔离变化。

    程序的隐私通常是程序最容易变化的部分,比如内部数据结构,内部使用的函数和全局变量等等,把这些代码封装起来,它们的变化不会影响系统的其它部分。降低复杂度。接口最小化是软件设计的基本原则之一,最小化接口容易被理解和使用。封装内部实现细节,只暴露最小的接口,会让系统变得简单明了,在一定程度上降低了系统的复杂度。
    3.如何封装?
    隐藏数据结构暴露内部数据结构,会使头文件看起来杂乱无章,让调用者发蒙。其次是如果调用者图方便,直接访问这些数据结构的成员,会造成模块之间紧密耦合,给以 后的修改带来困难。隐藏数据结构的方法很简单,如果是内部数据结构,外面完全不会引用,则直接放在C文件中就好了,千万不要放在头文件里。如果该数据结构 在内外都要使用,则可以对外暴露结构的名字,而封装结构的实现细节,做法如下:
    在头文件中声明该数据结构。
    如:
    struct _LrcPool;
    typedef struct _LrcPool LrcPool;
    在C文件中定义该数据结构。
    struct _LrcPool
    {
    size_t unit_size;
    size_t n_prealloc_units;
    };
    提供操作该数据结构的函数,哪怕只是存取数据结构的成员,也要包装成相应的函数。
    如:
    void* lrc_pool_alloc(LrcPool* thiz);
    void lrc_pool_free(LrcPool* thiz, void* p);
    提供创建和销毁函数。因为只是暴露了结构的名字,编译器不知道它的大小(所占内存空间),外部可以访问结构的指针(指针的大小的固定的),但不能直接声明结构的变量,所以有必要提供创建和销毁函数。
    如:
    这样是非法的:LrcPool lrc_pool;
    应该对外提供创建和销毁函数。
    LrcPool* lrc_pool_new(size_t unit_size, size_t n_prealloc_units);
    void lrc_pool_destroy(LrcPool* thiz);
    任何规则都有例外。有些数据结构纯粹是社交型的,为了提高性能和方便起见,常常不需要对它们进行封装,比如点(Point)和矩形(Rect)等。当然封装也不是坏事,MFC就对它们作了封装,是否需要封装要根据具体情况而定。
    隐藏内部函数
    内部函数通常实现一些特定的算法(如果具有通用性,应该放到一个公共函数库里),对调用者没有多大用处,但它的暴露会干扰调用者的思路,让系统看起 来比实际的复杂。函数名
    也会污染全局名字空间,造成重名问题。它还会诱导调用者绕过正规接口走捷径,造成不必要的耦合。隐藏内部函数的做法很简单:
    在头文件中,只放最小接口函数的声明。
    在C文件上,所有内部函数都加上static关键字。
    禁止全局变量
    除了为使用单件模式(只允许一个实例存在)的情况外,任何时候都要禁止使用全局变量。这一点我反复的强调,但发现初学者还是屡禁不止,为了贪图方便而使用全局变量。请读者从现在开始就记住这一准则。
    全局变量始终都会占用内存空间,共享库的全局变量是按页分配的,那怕只有一个字节的全局变量也占用一个page,所以这会造成不必要空间浪费。全局 变量也会给程序并发造成困难,想把程序从单线程改为多线程将会遇到麻烦。重要的是,如果调用者直接访问这些全局变量,会造成调用者和实现者之间的耦合。
    在整个系统程序员成长计划中,我们都是以面向对象的方式来设计和实现的(封装就是面向
    对象的主要特点之一)。为了避免不必要的概念混淆,这里先解释一下对象和类:
    关于对象:对象就是某一具体的事物,比如一个苹果, 一台电脑都是一个对象。每个对象都是唯一的实例,两个苹果,无论它们的外观有多么相像,内部成分有多么相似,两个苹果毕
    竟是两个苹果,它们是两个不同的对 象。对象可以是一个实物,也可以是一个概念,比如一个苹果对象是实物,而一项政策就是一个概念。在软件中,对象是一个运行时概念,它只
    存在于运行环境中, 比如:代码中并不存在窗口对象这样的东西,要创建一个窗口对象一定要运行起来才行。
    关于类:对象可能是一个无穷的集合,用枚举的方式来表示对象集合不太现实。抽象出对象的特征和功能,按此标准将 对象进行分类,这就引入类的概念。类就是一类事物的统称,
    类实际上就是一个分类的标准,符合这个分类标准的对象都属于这个类。当然,为了方便起见,通常只 需要抽取那些对当前应用来说是有用的特征和功能。在软件中,类是一个设计
    时概念,它只存在于代码中,运行时并不存在某个类和某个类之间的交互。我们说,编 写一个双向链表,实际上指的是双向链表这个类。
    C语言里并没有类这个概念,我也不想因为引入这个概念让读者感到迷惑。在后面的讲述中,我不会刻意区分类和对象,我们说对象,可能是指单个对象,也 可能是指对象所属的类,要根据上下文进行区分(这种区分通常是很直观的)。我并不是这种做法的首创者,见过好几本书都是这样做的,希望挑剔的读者不要在这 个概念问题上纠缠。

    好了,如果你实现的双向链表没有达到这个标准,请重做一遍练习吧。

     

  • 相关阅读:
    man arch
    封装 pyinstaller -F -i b.ico excel.py
    Python比较两个excel文档内容的异同
    运维工具
    python封装成exe
    OCP内容
    OCP
    操作系统
    转:铁大树洞APP视频讲解和原型演示
    2020.3.31——针对超能陆战队铁大树洞项目的匿名特点分析
  • 原文地址:https://www.cnblogs.com/dylan2011/p/2690735.html
Copyright © 2011-2022 走看看