2807|1

6366

帖子

4914

TA的资源

版主

楼主
 

如何写优雅的代码 [复制链接]

  本帖最后由 tiankai001 于 2014-10-9 10:44 编辑

如何写优雅的代码(序)——自语
//========================================================================
//TITLE:
//    如何写优雅的代码(序)——自语
//AUTHOR:
//    norains
//DATE:
//    Thursday  16-July-2009
//Environment:
//    WINCE5.0 + VS2005
//========================================================================
    这个系列是写给追求代码优雅的偏执狂。
   
    这基调意味着如下情形:
   
    1.本系列不适应于初学者。至少,没有语言美感的朋友,对本系列应该不会有太大的感冒。
   
    2.本系列对解决实际问题没有帮助。如果你不懂,看完之后你还是不懂;如果你懂了,看完之后你不会懂得更多。
   
    以文章做个比方。我这系列,不是告诉你如何去写文章,怎样才能突出文章的中心细想,怎样才能去感动人;而是告诉你如何给文章润色,让文章显得更优雅。
   
    仅此而已。
   
   
    另,因本人实在懒散,虽名为系列,但不确保会持续连续写下去。也许今天一点,明天一些,后天空白,逐步逐步完成所谓的系列
   
    之所以为系列,仅是提醒自己而已,无它。


如何写优雅的代码(1——灵活使用goto__try
//========================================================================
//TITLE:
//    如何写优雅的代码(1——灵活使用goto__try
//AUTHOR:
//    norains
//DATE:
//    Thursday  16-July-2009
//Environment:
//    WINCE5.0 + VS2005
//========================================================================
    goto是毒药?凡是能用goto的地方,肯定能用结构化方式来实现相同的目的!估计很多朋友都对这论断不会陌生,甚至可以说,太熟悉了!但能实现并不代表优雅。不信?我们接下来看看。
   
    假设我们有一个函数,需要实现如下功能:将一个驱动某些内容读取到缓存区去;又因为该缓存是全局公用的,所以我们很自然采用互斥量来进行控制。首先,我们坚持采用结构化方式实现,很可能我们的代码类似如下:
1 BOOL ReadDeviceBuf()  
2 {  
3     EnterCriticalSection(&g_csBuf);  
4   
5     //打开驱动设备  
6     HANDLE hDev = CreateFile(TEXT("DEV1:"),  
7         FILE_WRITE_ATTRIBUTES,  
8         0,  
9         NULL,  
10         OPEN_EXISTING,  
11         FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,  
12         NULL);  
13   
14     if(hDev == INVALID_HANDLE_VALUE)  
15     {  
16         LeaveCriticalSection(&g_csBuf);  
17         return FALSE;  
18     }  
19   
20     //获取驱动设备的缓存大小  
21     DWORD dwSize = 0;  
22     if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)  
23     {  
24         CloseHandle(hDev);  
25         LeaveCriticalSection(&g_csBuf);  
26         return FALSE;  
27     }  
28   
29     //分配缓存  
30     g_pBuf = new BYTE[dwSize];  
31     if(g_pBuf == NULL)  
32     {  
33         CloseHandle(hDev);  
34         LeaveCriticalSection(&g_csBuf);  
35         return FALSE;  
36     }  
37   
38     //从驱动中读取数据  
39     if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE)  
40     {  
41         delete []g_pBuf;  
42         g_pBuf = FALSE;  
43   
44         CloseHandle(hDev);  
45         LeaveCriticalSection(&g_csBuf);  
46         return FALSE;  
47     }  
48   
49   
50     CloseHandle(hDev);  
51     LeaveCriticalSection(&g_csBuf);  
52   
53     return TRUE;  
54 }  


    没错,采用这种结构化的方式的确是解决了问题。可是,我们是不是有点别扭呢?每次出错,返回FALSE之前,都必须要清理一次资源。小函数也许还不是什么大问题,只要睁大眼睛,小心翼翼,还是能在后续的返回中正确处理资源释放的。但如果函数因为要加入某些功能越来越大,又或许是别人来维护这段代码,那能保证在返回前释放资源么?


    接下来我们使用被大家鄙弃的goto,看看会发生什么情形:
[cpp] view plaincopy
55 BOOL ReadDeviceBuf()  
56 {  
57     BOOL bRes = FALSE;  
58   
59     EnterCriticalSection(&g_csBuf);  
60   
61     DWORD dwSize = 0;  
62   
63     //打开驱动设备  
64     HANDLE hDev = CreateFile(TEXT("DEV1:"),  
65         FILE_WRITE_ATTRIBUTES,  
66         0,  
67         NULL,  
68         OPEN_EXISTING,  
69         FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,  
70         NULL);  
71   
72     if(hDev == INVALID_HANDLE_VALUE)  
73     {  
74         goto EXIT;  
75     }  
76   
77     //获取驱动设备的缓存大小  
78     //DWORD dwSize = 0; //产生编译错误:initialization of 'dwSize' is skipped by 'goto EXIT'  
79     if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)  
80     {  
81         goto EXIT;  
82     }  
83   
84     //分配缓存  
85     g_pBuf = new BYTE[dwSize];  
86     if(g_pBuf == NULL)  
87     {  
88         goto EXIT;  
89     }  
90   
91     //从驱动中读取数据  
92     if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE)  
93     {  
94         goto EXIT;  
95     }  
96   
97     bRes = TRUE;  
98   
99 EXIT:  
100   
101     if(bRes == FALSE)  
102     {  
103         delete []g_pBuf;  
104         g_pBuf = FALSE;  
105     }  
106   
107     CloseHandle(hDev);  
108     LeaveCriticalSection(&g_csBuf);  
109   
110     return bRes;  
111 }  


    怎么样?把所有的资源释放都放到EXIT段中,每个EnterCriticalSection都能对应一个LeaveCriticalSection,是不是显得比之前的更为优雅?还能说goto为鸡肋么?
   
    不过,goto也不是尽善尽美,比如变量dwSizegoto之后就不能初始化,只能将局部变量的初始化放到第一个goto之前。按照C++的建议,变量的声明最好尽可能接近使用的地方。而放在第一个goto之前,摆明就是C作风嘛!
   
    其实如果以本特例,直接声明dwSize而不进行初始化也是可行的;但这并不代表在别的情况下也能畅通无阻,也许有的程序就依赖于初始化的值,谁知道呢?
   
   
    那有没有更为优雅的?可以解决这dwSize的位置问题的?答案自然是肯定的。不过,就必须请我们的__try出场咯:
[cpp] view plaincopy
112 BOOL ReadDeviceBuf()  
113 {  
114     BOOL bRes = FALSE;  
115   
116     EnterCriticalSection(&g_csBuf);  
117   
118     //打开驱动设备  
119     HANDLE hDev = CreateFile(TEXT("DEV1:"),  
120             FILE_WRITE_ATTRIBUTES,  
121             0,  
122             NULL,  
123             OPEN_EXISTING,  
124             FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,  
125             NULL);  
126   
127     __try  
128     {  
129         if(hDev == INVALID_HANDLE_VALUE)  
130         {  
131             __leave;  
132         }  
133   
134         //获取驱动设备的缓存大小  
135         DWORD dwSize = 0;   
136         if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)  
137         {  
138             __leave;  
139         }  
140   
141         //分配缓存  
142         g_pBuf = new BYTE[dwSize];  
143         if(g_pBuf == NULL)  
144         {  
145             __leave;  
146         }  
147   
148         //从驱动中读取数据  
149         if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE)  
150         {  
151             __leave;  
152         }  
153   
154         bRes = TRUE;  
155   
156     }  
157     __finally  
158     {  
159   
160         if(bRes == FALSE)  
161         {  
162             delete []g_pBuf;  
163             g_pBuf = FALSE;  
164         }  
165   
166         CloseHandle(hDev);  
167         LeaveCriticalSection(&g_csBuf);  
168     }  
169   
170     return bRes;  
171 }  


    哦耶!现在dwSize终于在它该出现的位置上了,是不是显得比goto更为优雅呢?
   
    这段改写的代码采用的是SEH机制。因为SEH机制如果需要详细解释,就不是一言两语的事情,所以在此就略过,感兴趣的朋友可以自己在网上查找资料。在这里,只是说明一点,采用SEH机制,无论如何,最后基本上一定要运行__finally段代码,除非中间有中断。
   
   
    最后一段是不是意味着凡是可以运用goto的地方都能采用__try替代?答案是否定的。特别是代码中采用了STLSEH机制将会无能为力。
   
    不信?我们添加一点代码段来看看事情的真相。假设我们不是通过数组来保留缓存,而是保留于STLvector中,并且成功读取之后,我们还想输出每个数值,那么我们代码可以如下:
[cpp] view plaincopy
172 BOOL ReadDeviceBuf()  
173 {  
174     BOOL bRes = FALSE;  
175   
176     EnterCriticalSection(&g_csBuf);  
177   
178     //打开驱动设备  
179     HANDLE hDev = CreateFile(TEXT("DEV1:"),  
180             FILE_WRITE_ATTRIBUTES,  
181             0,  
182             NULL,  
183             OPEN_EXISTING,  
184             FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,  
185             NULL);  
186   
187     __try  
188     {  
189         if(hDev == INVALID_HANDLE_VALUE)  
190         {  
191             __leave;  
192         }  
193   
194         //获取驱动设备的缓存大小  
195         DWORD dwSize = 0;   
196         if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)  
197         {  
198             __leave;  
199         }  
200   
201         //分配缓存  
202         g_vtBuf.resize(dwSize);  
203   
204         //从驱动中读取数据  
205         if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE)  
206         {  
207             __leave;  
208         }  
209   
210   
211         //打印每个数据  
212         //这里无法编译通过,提示error C2712: Cannot use __try in functions that require object unwinding  
213         for(std::vector::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++)  
214         {  
215             printf("%d/n",*iter);  
216         }  
217   
218         bRes = TRUE;  
219   
220     }  
221     __finally  
222     {  
223   
224         if(bRes == FALSE)  
225         {  
226             g_vtBuf.clear();  
227         }  
228   
229         CloseHandle(hDev);  
230         LeaveCriticalSection(&g_csBuf);  
231     }  
232   
233     return bRes;  
234 }  



    很遗憾,这段代码无法编译通过。因为STL的迭代器中用到了对象,而对象会释放C++异常,而这和SEH有冲突。当然,我们完全可以用new来替代,以避开这个问题,但这样一来,却是使代码更峥嵘,离优雅更是八辈子打不到一个杆子上。
   
    这时候,还是只能用goto
[cpp] view plaincopy
235 BOOL ReadDeviceBuf()  
236 {  
237     BOOL bRes = FALSE;  
238   
239     EnterCriticalSection(&g_csBuf);  
240   
241     //打开驱动设备  
242     HANDLE hDev = CreateFile(TEXT("DEV1:"),  
243             FILE_WRITE_ATTRIBUTES,  
244             0,  
245             NULL,  
246             OPEN_EXISTING,  
247             FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,  
248             NULL);  
249   
250   
251     if(hDev == INVALID_HANDLE_VALUE)  
252     {  
253         goto EXIT;  
254     }  
255   
256     //获取驱动设备的缓存大小  
257     DWORD dwSize = 0;   
258     if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)  
259     {  
260         goto EXIT;  
261     }  
262   
263     //分配缓存  
264     g_vtBuf.resize(dwSize);  
265   
266     //从驱动中读取数据  
267     if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE)  
268     {  
269         goto EXIT;  
270     }  
271   
272   
273     //打印每个数据  
274     //这里无法编译通过,提示error C2712: Cannot use __try in functions that require object unwinding  
275     for(std::vector::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++)  
276     {  
277         printf("%d/n",*iter);  
278     }  
279   
280     bRes = TRUE;  
281   
282 EXIT:  
283   
284   
285     if(bRes == FALSE)  
286     {  
287         g_vtBuf.clear();  
288     }  
289   
290     CloseHandle(hDev);  
291     LeaveCriticalSection(&g_csBuf);  
292   
293   
294     return bRes;  
295 }  


    最后这个例子,从另一个角度说明了,goto并不一定是鸡肋,在某些特定的环境下,只有它才能拯救代码于优雅之境地。
   
   
    文章末尾,我们稍微总结一下。为了达到代码优雅的目的,我们首选__try;只有代码中用到了C++异常,导致和SEH冲突,我们才拿起饱受非议的goto,以完成我们优雅之目的。


如何写优雅的代码(2——#defineconst?还是enum
//========================================================================
    //TITLE:
    //    如何写优雅的代码(2——#defineconst?还是enum
    //AUTHOR:
    //    norains
    //DATE:
    //    Tuesday  21-July-2009
    //Environment:
    //    WINCE5.0 + VS2005
    //========================================================================
    #defineconstenum:这三者有何关联?一个是宏定义,一个是静态修饰符,最后一个还是枚举类型。是不是有点像养麦皮打浆糊——粘不到一起?如果我们将范围缩小再缩小,让三者都只局限于固定值,那么千丝万缕的关系就了然于纸上——至少,有共同点了。


更多内容,请到EEWPRLD下载中心一览全貌!

最新回复

代码风格确实很重要  详情 回复 发表于 2014-10-11 00:08
点赞 关注(1)
 
 

回复
举报

2

帖子

1

TA的资源

一粒金砂(初级)

沙发
 
代码风格确实很重要
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表