语义压缩 已翻译 100%

oschina 投递于 2015/04/07 07:27 (共 18 段, 翻译完成于 04-20)
阅读 5792
收藏 66
3
加载中

我们都知道如何使用C++编程,不是吗 ?  我的意思是,我们都已经读过由以及热闹的留着胡子的家伙们精选的好书,是他们率先定义了编程语言,因此我们已经都学过了编写C++代码来解决真实世界问题的最好方式.

首先来看看现实世界中的问题——比方说一个工资系统—— 你会发现他里面有一些复数名词: “员工(employees)”, “经理(managers)”, 等等.  因此你需要做的第一件事情就是为这些名词创建类.  至少应该有一个 employee 类和一个 manager 类.

但是实际上,它们两个都是人(people). 因此我们可能需要一个叫做 “人(person)”的基类, 这样在我们的程序中那些不在意你到底是一个员工还是一个经理的部分,就可以值把你当做一个人对待. 这是非常人性化的,而且使得其他的类感觉上不像是一个企业及其中的齿轮!

LeoXu
翻译于 2015/04/07 07:46
4

不过这儿还有点问题.  经理就不也是一个员工吗?   因此经理应该可能要从员工继承, 而后员工从人继承.  现在我们真的达成了某些目标!  我们还没有真正想过要如何去写一些代码, 确实是,但我们已经对相关的对象进行了建模, 而一旦我们拥有了这些尸体,代码就只是在自己编写自己了.

等等,啊 — 你知道了什么吗 ?  我刚刚意识到,如果我们还有承包商( contractors)该如何呢?  我们肯定需要一个 contractor 类, 因为他们不是员工. contractor 类可以从person类继承, 因为所有的承包商都是人 (是不是 ?).  这都会是不言而喻的 .

但是这样的话manager类该从哪继承呢?  如果它从employee类继承,那么我们就不能有从事承包商工作的经理. 如果它从contractor类继承,那么我们就不能有全职的经理. 这将会是一个真正困难的编程问题, 像是 Simplex 算法之类的问题!

LeoXu
翻译于 2015/04/07 08:00
3

OK, 我们可以让manager对两个类都进行继承, 然后只使用它们之中一个的特征. 但那样就没有足够的类型安全性了.  这并不是某些随意编写的JavaScript的程序 ! 但是你知道吗 ?  砰!  我就此得到了解决方案 : 对manager类进行模板化.  我们可以在基类上对manager类进行模板化, 然后所有随着manager类一起运作的东西都在其上被模板化了!

这将会是最好的工资系统了 !  当我梳理出了所有这些类和模板 , 就开始启动编辑器,去创建UML图形了.

程序员发编程贴

如果我刚刚写的都是荒谬的就好了,但可悲的是,实际上世上有许许多多的程序员都这样想啊. 我不只是在说 “实习生Bob” — 而是指所有类型的程序员,包括那些讲课和写书的知名程序员.  我也要痛心的指出在我的生涯中的一段时期也是这样子思考的.  在我18岁的时候被介绍了解了“面向对象编程”, 而直到大概24岁的时候才意识到这一切都是胡说八道 (而这种认识的形成少不了要感谢我在工作时使用的RAD游戏工具, 我很庆幸,它没有让我整个陷入OOP的噩梦中去).

LeoXu
翻译于 2015/04/07 20:48
3

但是尽管外头许多的开发者事实上都已经经历过像这样的糟糕阶段,并且最终得出了有关如何真正高效的写出好代码这样的总结,但是现有的海量教材仍然势不可挡的沦落到“中看不中用”之类。我猜想这跟一个事实有关,这个事实就是一旦你知道怎么做了,那么好的编程方式看起来就会是非常直接的, 不像那种徒然注重外表,让你想要去耗费时间才捣鼓出来的技巧,比方说一个花哨的数学技巧。因此,尽管没有任何数据佐证,我仍强烈的猜想着,有经验的程序员很少会花时间去告诉别人他们是如何去编写程序的,因为他们根本不认为这是什么特别的事情.

但他们应该这样做!这可能并不特别,但却是有必要的,而如果好的程序员没有开始去写点有关“如何好好的去编写程序”的东西给其他人看,那么我们将永远不会逃离泥潭,那时每个人就都要在认识到他们在浪费时间之前的六年时间,编写可怕的面向对象程序了。因此我要在接下来的一辑见证(Witness)文章上所做的,就是花一些重要的文笔讲述将代码放入计算机里去,这一纯机械的过程, 我也真诚地希望其他有经验的程序员能够花点时间也像我这样做一做。就个人而言,我喜欢读到更多有关那些实际上很优秀的程序员坐下来写代码时所使用的技术。

LeoXu
翻译于 2015/04/08 20:17
1

首先,我要开始细化一套简单的代码转换方案,我曾在《见证者》(The Witness)的编辑器代码中这么做过。在未来几周内,我将从一些比较大的案例中出来,在这些案例中我从头开始写了,但我用了全部的时间去痛苦地专注于代码和它的构建。我没什么要说的,在这其中根本就没有什么有趣的算法、数学或是其他的什么东西,一切都只是在“清理管道”。

乔恩(Jon)开始做事了

在《见证者》的内置编辑器中,有一块UI被称为“移动面板”(Movement Panel)。它是一个浮动的带有若干按钮的窗口,被用来展示一些如“旋转90度”的操作。最初这个窗口非常的小,只有几个按钮,但当我在编辑器上开展工作后,我加了一系列的特性,都需要用到移动面板。这将会在很大程度上扩大它的内容,并且也意味着我不得不学习如何向这个UI中添加元素,这是我之前从未做过的。我检查了现有的代码,这些代码看起来是这样的:

int num_categories = 4;
int category_height = ypad + 1.2 * body_font->character_height;
float x0 = x;
float y0 = y;
float title_height = draw_title(x0, y0, title);
float height = title_height + num_categories * category_height + ypad;
my_height = height;
y0 -= title_height;

{
    y0 -= category_height;
    char *string = "Auto Snap";
    bool pressed = draw_big_text_button(x0, y0, my_width, category_height, string);
    if (pressed) do_auto_snap(this);
}

{
    y0 -= category_height;
    char *string = "Reset Orientation";
    bool pressed = draw_big_text_button(x0, y0, my_width, category_height, string);
    if (pressed) {
    ...
    }
}
...
北风其凉
翻译于 2015/04/09 23:08
3

我首先注意到的事情就是原来的程序员Jon做得真的很不错,将我正要做成的事情打好的前阵. 许多许多次你一打开某些像这样如此简单的东西的代码时,你会发现它只是一大堆乱麻一样的不必要的架构和中间层.  而在这里我们发现的则是一系列非常直接的事情在发生着,读起来非常像你在那样子指导一个绘制出一个UI的面板: “首先,计算出标题条应该怎么摆放,然后,绘制这个标题条.  现在,在那个标题条下面,绘制自动捕捉(Auto Snap)按钮. 如果它被按下了,就做自动捕捉 . . .”   这正就是我们所设计的如何运行的.  我才任何的大多数的人都能从这段代码读到它在做的事情,而且可能不用除了这段摘录的代码之外的其它任何东西,就能凭直觉知道如何添加更多的按钮.

不过,这段代码虽然如此漂亮,但是它明显不是为了做大量UI操作而生的, 因为所有的布局工作仍然靠手工完成,一行一行的编写.  在上面的代码段中这有点轻微的不方便, 而且一旦你考虑到更加复杂的布局,事情就会变得更加的繁重, 就像这块UI的代码,在同一行出现了四个独立的按钮:

{
    y0 -= category_height;

    float w = my_width / 4.0f;
    float x1 = x0 + w;
    float x2 = x1 + w;
    float x3 = x2 + w;

    unsigned long button_color;
    unsigned long button_color_bright;
    unsigned long text_color;

    get_button_properties(this, motion_mask_x,
        &button_color, &button_color_bright, &text_color);
    bool x_pressed = draw_big_text_button(x0, y0, w, 
        category_height, "X", button_color,
        button_color_bright, text_color);

    get_button_properties(this, motion_mask_y,
        &button_color, &button_color_bright, &text_color);
    bool y_pressed = draw_big_text_button(x1, y0, w, 
        category_height, "Y", button_color, 
        button_color_bright, text_color);

    get_button_properties(this, motion_mask_z,
        &button_color, &button_color_bright, &text_color);
    bool z_pressed = draw_big_text_button(x2, y0, w, 
        category_height, "Z", button_color, 
        button_color_bright, text_color);

    get_button_properties(this, motion_local,
        &button_color, &button_color_bright, &text_color);
    bool local_pressed = draw_big_text_button(x3, y0, w, 
        category_height, "Local", button_color, 
        button_color_bright, text_color);

    if (x_pressed) motion_mask_x = !motion_mask_x;
    if (y_pressed) motion_mask_y = !motion_mask_y;
    if (z_pressed) motion_mask_z = !motion_mask_z;
    if (local_pressed) motion_local = !motion_local;
}

因此,在我们开始添加许多的新按钮之前,我已经觉得自己应该花一点点时间在底层的代码上,以使其能更容易的添加新的东西进去.  为什么我会这样子觉得呢,而我又如何知道在这种情况下“更简单”意味着什么呢?

LeoXu
翻译于 2015/04/09 22:05
2

语义压缩

我把编程从本质上分为两个部分:找出处理器在完成任务中真正需要做的事情,然后以我所使用的语言,用最有效的方式来表达.而事实上,程序员在后者上花费越来越多的时间:在将所有这些算法和数学形式连成一个整体,并且不会因其自身的重量而这件事情上进行缠斗. 

所以任何有经验的优秀程序员一定会考虑高效地编程意味着什么即便只靠直觉.说起 “高效 “,并不仅仅指代码优化.更准确地说,这意味着代码的开发过程也是优化的即代码得是结构化的,这种结构化能够最小化敲代码,跑起程序,修改代码,进行足够的debug使其能够交付使用所付出的人类劳动力.

fr000
翻译于 2015/04/08 00:42
2

我喜欢尽量从整体上考虑效率性. 如果你将一块代码作为整体考察开发的过程,你不会对任何隐藏的花费视而不见. 在代码被使用的地方给定其性能和质量的等级, 从有了它开始到最后一次被任何人因为任何原因而使用到结束,我们的目标就是讲它所要耗费的人力成本最小化. 这包括了将其输入的时间,包含了修改它的时间,包含了将其适配到其它用途的时间. 它包含为了让它能与其运行起来的,而假如是被用不同方式写出来的,则可能不会有那么必要的代码的任何工作量. 使用这份代码的整个生命周期的所有工作都被包含了进来.

当以这种方式进行考虑的时候,我的经验让我总结出,编程最有效率的方式就是让自己就像是一个字典压缩器一样的处理你的代码. 从字面意思上看,你假装自己是PKZip的很棒的版本,它不断的在你的代码上运行,不断地寻找让它(在语义上)更加小的方式.  而要明白,我的意思是语义上更加的小,如重复和相似的代码少,而不是物理上更加的小,如文本少,尽管这两者经常如影随形.

LeoXu
翻译于 2015/04/10 22:06
2

这是一个非常地自下而上的编程方法, 它是一个最近被冠上绰号“重构”的伪变体 , 尽管它同时也是因为许多原因而被视为不值得进行的荒谬过程.  我也认为正式的“重构”这个概念丢失了要点,而并不值得我们去实践.  要点会是,它们是某类相关的,而你有希望将在阅读这一系列文章的过程中理解到的异同.

那么面向压缩编程会是什么样子呢,而它为什么是有效的呢?

就像一个好的压缩机,我不会重用任何东西,除非我至少需要两个它这样的东西.  许多程序员不理解这有多重要,并且立马就尝试去写“可重用的”代码,但那可能就是你会犯下的最大的错误之一. 我提倡的是,“在让你代码可以重用之前,先让它是可以用的”.

LeoXu
翻译于 2015/04/11 13:34
2

在每一个特定的情况中,我总是只去写出我确切的想要发生的东西, 没有任何有关”正确性“或者”抽象性“的东西,或者任何其它的流行词, 而我也让其运行了起来. 然后,当我发现自己在其它的某中情况下正在第二次做相同的事情, 那就是时候我该把可重复使用的部分拉出来进行共享,从而有效地”压缩“代码.  我更喜欢拿”压缩“来做比喻, 因为它意味着某些有用的东西,而不像常用来做比喻的“抽象”,它并没有表达出任何有用的东西. 谁会关心代码是否抽象了?

等待着,直到一块代码(至少)有了两个用到它的实例,意思是要直到我知道自己真的需要考虑如何去重用它,同时它的意思也是在我尝试去使其可以重用之前,我总是会有至少两个真正的,代码所要做的实例.  这一点对于效率至关重要,因为如果你只有一个实例,或者更加糟糕,(在没有想好就开始写代码的情况下)没有实例, 那么很有可能会在编写它的时候犯错误,并且会产生不那么方便拿来重用的代码.  这会导致你在使用它时更多时间浪费, 因为它要么将会很麻烦,要么你将为了让它能按你需要的方式运行而不得不重新编写它. 因此我努力尝试从不去使得代码“过早的可重用”, 导致要用到高纳德(Kruth)的那些麻烦东西.

LeoXu
翻译于 2015/04/11 14:20
2
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(32)

幻の上帝
幻の上帝
哦,漏说了一点。
从stack frame一说同样能够看出原看作者的C艹或者更一般意义上的程序设计水平/眼界的图样。
“无论你希望哪里需要UI面板,这个想法都能付诸实现”这个细节上的目的算的是上三观正确乃至于伟光正——形而上的抽象说法大概就是低内聚高耦合便于复用,但是扯上stack frame就是leaky abstraction了。
这是明显的错误姿势。不管C还是C++,如果不管体系结构和二进制互操作的东西实际上平时就不该考虑这种混淆思路的东西。
当然,不是说任何情况无视实现都是跟简单的。比如说stack unwinding,还有va_args导致没法RAII破事的理由,引入stack frame这种现实因素便于清醒的判断。但这种corner case明显应该是极少数的,一边搞应用层UI一边开这种小差实在有些诡异。
退一步讲,跟只是因为这种恰当抽象层次上的无知就一时冲动艹个native coroutine相比,这种稀里糊涂的思维在产出上也明显不够高尚有逼格。
话说这些细节上的局限看起来微不足道,累积起来却削弱了文章中要表达正面观点的价值,还让主题不明确。所以怎么感觉原作者语文水准也有点那啥……
幻の上帝
幻の上帝

引用来自“Jwxl”的评论

java表示看到这代码很蛋疼,不看了

引用来自“梁金堂”的评论

想问问,正方形继承于什么好呢?长方形?菱形?正多边形?到底是继承还是组合?

引用来自“gongweixin”的评论

个人人为,正方形继承长方形,因为数学课里说的正方形是一种特殊的长方形。复合think in java里说的 is a 的关系,所以用继承

引用来自“struct”的评论

同时正方形也是一种特殊的菱形和正多边形。

引用来自“WolfCS”的评论

面向对象程序设计中的“is-a”关系不完全等价于现实世界中的“是一个”的关系。

引用来自“WolfCS”的评论

面向对象程序设计中的“is-a”关系有它自己的明确的约束。
is-a也算不上面向对象的专利。 LSP实现起来方式多得很。 http://typeof.net/2014/m/formation-of-modern-magic--why-functions-are-contravariant-in-the-input-type.html
幻の上帝
幻の上帝
一看就是过来人——虽然方向上没有大问题,然而逗。
先说逗的地方。
首先,最明显的,是搞错了动机。
扔掉烂设计不是不要设计。
明眼人都知道到处自底向下的整法明显不是目的而只是难以或者不可能有完整的设计时的妥协。
要是自顶向下就能解决好问题,谁还这么无聊增加工作量?
别说现实中的边界不清、度量困难、复用拙计还有责任问题了,有时候这种做法根本不顶用,没做出自顶向下的完整设计就是没有解决问题。
其次,光说重构这个词,只是行为艺术上的描述。
无论按照这篇文章中的哪种说法去做,实现上都算的是上重构——要么就是打算扔了重写。
然而现实并不是什么情况下都能负担得起重写的成本,何况重写也不能保证不更糟。于是翔也得继续糊着。
第三,在撸UI这破事上明显图样。
现在明显没多少人鼓吹用C++做这种层次上的UI了。
强调面对需求变化的、要生产力的现实状况几乎哪里都是套至少一层DSL——无论是HTML/CSS/ECMAScript、XAML还是QML之类乱七八糟的东西,甚至直接拿XML配置的也有。
用C++实现UI也不是没有,但好意思拿这种破代码晃悠的,几乎就是坨C厨。然而GTK+也有Vala……
当然,不是说C++就不适合写UI。有些地方现实还就只能靠C++——比如实现浏览器的图形栈/文本栈的主要部分。但这用的不是这篇文章里那坨代码的套路;并且和文章中主张得相反,自底向上连翔都堆不起来。
据我所知这个层次或以下的所有现实产品都是只能靠spec堆的,就算艹MFC这种货色也得瞧瞧MSDN里有什么好用,而不是有什么就现场总结局部需求上个轮子。
第四,C++水平也是那啥。
最明显一点:能用lambda还用这种C厨方法么。真是图森破。
用文章里这种写法,与其说是改善接口和实现,不如说是人肉编译器撸micro pass:主要就是靠频繁做constant propagation啊strength reduction啊什么的低级操作来让代码(可能)变得更清晰、更能被复用。
这样的确不止外人,连写代码的自己也很容易感觉只是没头苍蝇到处搬运代码,不容易保持代码构造上的拓扑关系,事后才能反应出来一点头绪——也难怪整体看起来也不怎么像典型的重构了。
gongweixin
gongweixin

引用来自“Jwxl”的评论

java表示看到这代码很蛋疼,不看了

引用来自“梁金堂”的评论

想问问,正方形继承于什么好呢?长方形?菱形?正多边形?到底是继承还是组合?

引用来自“gongweixin”的评论

个人人为,正方形继承长方形,因为数学课里说的正方形是一种特殊的长方形。复合think in java里说的 is a 的关系,所以用继承

引用来自“struct”的评论

同时正方形也是一种特殊的菱形和正多边形。
说的对,哎,数学没学好。。
wuwenbin
wuwenbin
我觉得怎么写都好,容易理解并且代码量少方便修改即可
彭添
彭添
我正在学习OOP,看到楼主说的“OOP的噩梦”,差点就被影响了,还好我把思想纠正回来了,我是个新手,无法对楼主今天的观点做任何技术性的评论,等我过几年再回来真正回复吧。
中山野鬼
中山野鬼
哈,道理不错。还是那句,看懂的自然懂,看不懂的,自然看不懂。。。。
叫哥哥给糖
叫哥哥给糖
翻译的什么狗屁
哆啦比猫
哆啦比猫

引用来自“Jwxl”的评论

java表示看到这代码很蛋疼,不看了

引用来自“梁金堂”的评论

想问问,正方形继承于什么好呢?长方形?菱形?正多边形?到底是继承还是组合?

引用来自“struct”的评论

既不是继承,也不是组合。 而是交集。 正方形是长方形、菱形和正多边形的交集。
所以说作为独立一个类好了,反正有模板元编程和variant
哆啦比猫
哆啦比猫
是翻译的原因吗?这汉语基本无法理解
返回顶部
顶部