browser icon
You are using an insecure version of your web browser. Please update your browser!
Using an outdated browser makes your computer unsafe. For a safer, faster, more enjoyable user experience, please update your browser today or try a newer browser.

接口设计的越俎代庖

Posted by on 2008 年 09 月 04 日

你可以任意转载本文,但请在转载后的文章中注明作者和原始链接。
媒体约稿请联系 titilima_AT_163.com(把“_AT_”换成“@”)。

来看一个函数,先:

1
2
3
4
5
6
7
8
9
// foo1
// 打开一个文件,向其中写入字符串"Hello, World!"
// [in] lpFileName: 要打开的文件名。
void foo1(LPCTSTR lpFileName)
{
    HANDLE hFile = CreateFile(lpFileName, GENERIC_WRITE, 0, NULL,   
        CREATE_ALWAYS, FILE_ATTRIBUTE_ARCHIVE, NULL);
    foo2(hFile);
}

这段代码是我看某个产品代码的时候发现的,所以请不要怀疑它的真实性;唯一要指出的是,这段代码被我进行了精简再加工,只留其神不留其形,所以断无泄露商业秘密的嫌疑。
最初看到这段代码时,我质疑它可能会造成资源泄漏,因为我并未看到CloseHandle。不过,深入foo2后,打消了我的质疑:

1
2
3
4
5
6
7
8
9
10
// foo2
// 向指定的文件中写入字符串"Hello, World!"
// [in] hFile: 一个有效的文件句柄。
void foo2(HANDLE hFile)
{
    CHAR str[] = "Hello, World!";
    DWORD dwWritten;
    WriteFile(hFile, str, lstrlenA(str), &dwWritten, NULL);
    CloseHandle(hFile);
}

到这里似乎不该有什么问题,而且foo1是可以正常工作的。不过我要说:这实在是一个糟糕的接口设计——foo2积极过度,越俎代庖地完成了一个本不应该它来完成的工作。
那么现在我们来假设这么一个情景:编写foo1和foo2的程序员离职了,另一个程序员来扩展这部分代码。他看到foo2的声明后,很可能会想当然地认为foo2只完成写入的操作,于是他会这么来写他的foo3:

1
2
3
4
5
6
7
8
9
10
11
// foo3
// 打开一个文件,向其中写入10个字符串"Hello, World!"
// [in] lpFileName: 要打开的文件名。
void foo3(LPCTSTR lpFileName)
{
    HANDLE hFile = CreateFile(lpFileName, GENERIC_WRITE, 0, NULL,   
        CREATE_ALWAYS, FILE_ATTRIBUTE_ARCHIVE, NULL);
    for (int i = 0; i < 10; ++i)
        foo2(hFile);
    CloseHandle(hFile);
}

最后,他只得到了一个"Hello, World!"。而且,他可能会花了很长时间才找到是这个糟糕的foo2坏了他的事,因为这个设计的缺陷隐藏得太深了。
所以,请善待你所设计的接口,不要让它们承担任何本不该它们承担的责任。

订阅本站

一条评论

  • At 2008.09.04 18:51, a said:

    这让我想起了我那本深藏在角落,正积着灰的< 代码大全> 囧..

    (Required)
    (Required, will not be published)