printf("hello, world\n");
即使没有任何C基础的人,也能够看懂这是在做一个字符串打印。
语言的易读性其实是语言非常重要的特征,比其他特征都重要。因为人类的大脑不可能记得所有的代码细节,并且能够直观的反应出如何修改。往往我们需要阅读一下代码,搞明白每段的意思,然后才能动手——哪怕这段代码出自自己手笔,只要过得一两个月,还是要重读一下的。正是因为读这个技能的使用频率非常高,所以语言的易读性非常直观的影响到语言的易用性。而易读性差的语言和习惯,目前来看有以下几个典型例子。
1.罗嗦
典型代表是Java。下面是一个Java解压Zip的代码,引用自参考1。
public class Zip {
static final int BUFFER = 2048;
public static void main(String argv[]) {
try {
BufferedInputStream origin = null;
FileOutputStream dest = new FileOutputStream("E:\\test\\myfiles.zip");
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(
dest));
byte data[] = new byte[BUFFER];
File f = new File("e:\\test\\a\\");
File files[] = f.listFiles();
for (int i = 0; i < files.length; i++) {
FileInputStream fi = new FileInputStream(files[i]);
origin = new BufferedInputStream(fi, BUFFER);
ZipEntry entry = new ZipEntry(files[i].getName());
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER)) != -1) {
out.write(data, 0, count);
}
origin.close();
}
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
我下面给出python版本。static final int BUFFER = 2048;
public static void main(String argv[]) {
try {
BufferedInputStream origin = null;
FileOutputStream dest = new FileOutputStream("E:\\test\\myfiles.zip");
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(
dest));
byte data[] = new byte[BUFFER];
File f = new File("e:\\test\\a\\");
File files[] = f.listFiles();
for (int i = 0; i < files.length; i++) {
FileInputStream fi = new FileInputStream(files[i]);
origin = new BufferedInputStream(fi, BUFFER);
ZipEntry entry = new ZipEntry(files[i].getName());
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER)) != -1) {
out.write(data, 0, count);
}
origin.close();
}
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
import os, zipfile
with zipfile.ZipFile('filename.zip', 'w' ,zipfile.ZIP_DEFLATED) as zf:
with zipfile.ZipFile('filename.zip', 'w' ,zipfile.ZIP_DEFLATED) as zf:
for name in os.listdir('.'): zf.write(name)
罗嗦有什么坏处?当你需要理解一段代码的时候,需要上上下下翻动屏幕,并且仔细对比每个细节,才能理解这个代码的目的。这对于阅读来说是非常不友好的。
2.歧义
典型例子是++,我给出这么一个例子。
i = (++j) + (j++) + i---i
i是多少?脑子一团糨糊吧?关于自增自减的歧义,具体可以看参考2。当然,这并不是说C++设计的有问题,只是这个用法不可取而已。
歧义的最大问题是,不借助具体的实现运行一下,基本没有希望了解这个代码是什么意思。这哪里叫可读,这叫不可读。歧义是不可读中最差劲的一种,一切产生歧义的代码都是坏的代码,例如我们下面的这个例子:
import os
def os(os): return os
这个,return回去的到底是谁?os module?function?variable?运行一下我们知道,其实是返回了参数。但是这种代码骤然看到,鬼才能够反应的过来,写出这种代码的,上辈子都是非洲丛林里面的守林人,想bug想疯了吧。
还有一种是变量名类似,例如只以大小写区分,或者以下这个例子:
def sl(s1): return sl
您看出问题了么?没看出来?这到底要多脑残才会把变量弄的那么像函数名,导致return的时候把自己的函数给return回去阿?
3.依赖上下文
什么叫依赖上下文?其实这并不是一个很好界定的问题。无论代码多么简洁,我们都需要调用其他函数。这个函数就是所谓的上下文。在拥有一定知识的前提下,我们的代码越上下文无关越好。如果一定要上下文有关,这个相关部分越确定越好。例如C++中的一个例子:
DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_WindowChanged(_T("wm_hooks.dll"), "WM_Hooks_WindowChanged");
谁能告诉我,为了看懂这个代码,我需要查看多少内容?首先,我需要查看DynamicFn和WM_Hooks_WMVAL_proto的定义,然后去检查DynamicFn的构造函数。如果只有一个构造函数,并且参数类型匹配,那么很幸运,事情就到此为止。如果不匹配,我还得查看是否可以编译通过,如果可以,是匹配了哪个构造函数。如果都不匹配,那么肯定发生了内隐转换(implicit cast),如果有两个函数都可以通过内隐转换进行匹配,例如下面这种:
template <typename T>DynamicFn::DynamicFn(wstring t, char * c);
DynamicFn::DynamicFn(TCHAR * t, string c);
天呐,这个不但依赖上下文,而且歧义了。更郁闷的是,随着UNICODE宏的变化,这两个函数的匹配行为还会产生变化。即使上面一切都没问题,您能够直观的从刚刚的一行代码中看出代码所要达到的目的么?从老程序员的习惯来猜测,好像是wm-hooks这个dll的WM_Hooks_WindowChanged函数进行SetHook,是不是,我懒得验证了。
比较好的解决这个问题的方法叫做代码自描述性。例如上文,这种文法是比较容易理解的。(但是不保证意义一致,因为上文我还不确定是安装Hook还是仅仅生成对象包装,或者两个行为同时实施了?下文也只是伪代码)
create_function(u"wm_hooks.dll", "WM_Hooks_WindowChanged")
4.晦涩
何谓晦涩?高级特性过多。典型代表C++,谁来读一下这个代码?
#pragma once
#pragma lib("curl")
using namespace std;
#ifndef RFB_MAINTHREAD
#define RFB_MAINTHREAD
namespace rbf{
extern "C" {
class MainThread: public Thread, EventHandler {
MainThread (explicit HANDLE hFD);
virtual ~MainThread();
inline static int run_wrapper() { return run(); }
virtual run() = 0;
};
}
}
没有评论:
发表评论