实践C++ 代码维护的思考

最初发表在QQ空间里,http://user.qzone.qq.com/31731705/blog/1309416291,正好有征文大赛,就放在这里吧。
 

小问题有大智慧-代理服务器的监测 是几个月前的文章,最近碰到别人问如何设置代理的问题,又回顾了部分代码,虽然时间不长,还是有不少记不清了。,于是就整理了那个设置代理的函数,代码是实践的科学,每写一次,都会有点心得。

先把代码贴出来,重点的部分用粗体。这个函数的大概流程是,先查询当前的浏览器设置,然后根据用户的设定,再决定 1. 无代理 2. 使用自动配置脚本 3. 使用某个代理 这三个选项中的一个,根据不同的选项,设置具体的值,然后调用API设置代理选项。

  1. void CWRSBar::ModifySetting( const Option::ProxyEntryInfo &pei )
  2. {
  3. // refer to following value in WinInet.h
  4. // so as to use these values as index in array.
  5. // :)

  6. /*
  7. #define INTERNET_PER_CONN_FLAGS 1
  8. #define INTERNET_PER_CONN_PROXY_SERVER 2
  9. #define INTERNET_PER_CONN_PROXY_BYPASS 3
  10. #define INTERNET_PER_CONN_AUTOCONFIG_URL 4
  11. #define INTERNET_PER_CONN_AUTODISCOVERY_FLAGS 5
  12. */

  13. // 初始化数据结构,主要是Option数组

  14. INTERNET_PER_CONN_OPTION_LIST List;
  15. INTERNET_PER_CONN_OPTION Option[6];
  16. unsigned long nSize = sizeof(List);

  17. Option[0].dwOption = 0;
  18. Option[0].Value.dwValue = 0;

  19. // connection flags
  20. Option[INTERNET_PER_CONN_FLAGS].dwOption = INTERNET_PER_CONN_FLAGS;
  21. Option[INTERNET_PER_CONN_FLAGS].Value.dwValue = PROXY_TYPE_DIRECT;
  22. //|PROXY_TYPE_AUTO_DETECT;

  23. // proxy server
  24. Option[INTERNET_PER_CONN_PROXY_SERVER].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
  25. Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue = NULL;

  26. // proxy bypass
  27. Option[INTERNET_PER_CONN_PROXY_BYPASS].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
  28. Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue = NULL;

  29. // auto config URL
  30. Option[INTERNET_PER_CONN_AUTOCONFIG_URL].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;
  31. Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue = NULL;

  32. // others ...
  33. Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS;
  34. Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS].Value.dwValue = AUTO_PROXY_FLAG_USER_SET | AUTO_PROXY_FLAG_DETECTION_RUN;

  35. List.dwSize = nSize; //sizeof(INTERNET_PER_CONN_OPTION_LIST);
  36. List.pszConnection = NULL;
  37. List.dwOptionCount = 5;
  38. List.dwOptionError = 0;
  39. List.pOptions = &Option[1];

  40. // Use it like C macro
  41. class JustForOutputOption
  42. {
  43. public:
  44. void operator()( INTERNET_PER_CONN_OPTION *p )
  45. {
  46. ATLASSERT( p );
  47. INTERNET_PER_CONN_OPTION *Option = p;

  48. WRST( LOG_TREND_PROXY )(
  49. TEXT("Option[INTERNET_PER_CONN_FLAGS](0x%x): (0x%x), ")
  50. TEXT("Option[INTERNET_PER_CONN_PROXY_SERVER](0x%x): (%s), ")
  51. TEXT("Option[INTERNET_PER_CONN_PROXY_BYPASS](0x%x): (%s), ")
  52. TEXT("Option[INTERNET_PER_CONN_AUTOCONFIG_URL](0x%x): (%s), ")
  53. TEXT("Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS](0x%x): (0x%x)."),
  54. Option[INTERNET_PER_CONN_FLAGS].dwOption, Option[INTERNET_PER_CONN_FLAGS].Value.dwValue,

  55. Option[INTERNET_PER_CONN_PROXY_SERVER].dwOption,
  56. Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue ? Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue : TEXT("NULL"),

  57. Option[INTERNET_PER_CONN_PROXY_BYPASS].dwOption,
  58. Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue ? Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue : TEXT("NULL"),

  59. Option[INTERNET_PER_CONN_AUTOCONFIG_URL].dwOption,
  60. Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue ? Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue : TEXT("NULL"),

  61. Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS].dwOption, Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS].Value.dwValue
  62. );
  63. }
  64. }DebugOutputOption;

  65. // 查询当前的设置.
  66. if( InternetQueryOption( NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize ) )
  67. {
  68. DebugOutputOption( Option );

  69. int proxyType = Option::MANUAL;

  70. std::tstring proxyAuto, proxyDirect;
  71. g_wrsCfg.GetOpt( WRS_TRENDPROXY_AUTO_NAME, proxyAuto );
  72. g_wrsCfg.GetOpt( WRS_TRENDPROXY_DIRECT_NAME, proxyDirect );

  73. if( pei.name == proxyAuto )
  74. proxyType = Option::AUTO;

  75. if( pei.name == proxyDirect )
  76. proxyType = Option::DIRECT;

  77. std::Bit32 flag;
  78. enum { USE_OLD_PAC = 1, USE_OLD_BYPASS, USE_OLD_PROXY, };
  79. flag[USE_OLD_PAC] = flag[USE_OLD_PROXY] = flag[USE_OLD_BYPASS] = true;

  80. TCHAR proxy[BufSize], bypass[BufSize];
  81. proxy[0] = bypass[0] = 0;

  82. // 根据用户的配置,做具体的设置

  83. switch( proxyType )
  84. {

  85. // 使用自动配置脚本,如果原来有值,使用原来的值。
  86. case Option::AUTO:
  87. {
  88. Option[INTERNET_PER_CONN_FLAGS].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_AUTO_PROXY_URL;
  89. if( !Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue || !lstrlen( Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue ) )
  90. {
  91. // use AUTO value now
  92. //Option[0].Value.pszValue = const_cast<LPTSTR>( pei.server.c_str() );
  93. Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue = const_cast<LPTSTR>( g_wrsTrendProxyCfg.autoURL.c_str() );
  94. flag[USE_OLD_PAC] = false;
  95. }
  96. }
  97. break;

  98. // 直连

  99. case Option::DIRECT:
  100. {
  101. Option[INTERNET_PER_CONN_FLAGS].Value.dwValue = PROXY_TYPE_DIRECT;
  102. }
  103. break;

  104. // 使用用户配置的代理

  105. case Option::MANUAL:
  106. {
  107. Option[INTERNET_PER_CONN_FLAGS].Value.dwValue = PROXY_TYPE_DIRECT|PROXY_TYPE_PROXY;

  108. if( Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue )
  109. GlobalFree( Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue );

  110. _sntprintf( proxy, BufSize-1, TEXT("%s:%d"), pei.server.c_str(), pei.port );
  111. ATLASSERT( lstrlen( proxy ) );

  112. Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue = proxy;
  113. flag[USE_OLD_PROXY] = false;

  114. if( Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue )
  115. GlobalFree( Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue );

  116. lstrcat( bypass, g_wrsTrendProxyCfg.bypass.c_str() );
  117. if( lstrlen( bypass ) )
  118. lstrcat( bypass, TEXT(";") );
  119. lstrcat( bypass, TEXT("<local>") );

  120. Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue = bypass;
  121. flag[USE_OLD_BYPASS] = false;
  122. }
  123. break;
  124. }

  125. DebugOutputOption( Option );

  126. // 设置代理选项
  127. InternetSetOption( NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, nSize );

  128. // free memeory
  129. if( Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue && flag[USE_OLD_BYPASS] )
  130. GlobalFree( Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue );

  131. if( Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue && flag[USE_OLD_PROXY] )
  132. GlobalFree( Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue );

  133. if( Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue && flag[USE_OLD_PAC] )
  134. GlobalFree( Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue );

  135. // system
  136. // Notifies the system that the registry settings have been changed so that it verifies the settings on the next call to InternetConnect.
  137. // This is used by InternetSetOption.
  138. InternetSetOption( NULL, INTERNET_OPTION_SETTINGS_CHANGED, NULL, 0 );
  139. // ie
  140. // Causes the proxy data to be reread from the registry for a handle. No buffer is required.
  141. // This option can be used on the HINTERNET handle returned by InternetOpen. It is used by InternetSetOption.

  142. // so if we don't call this API ie might show previous value even after last API call.
  143. //
  144. //InternetSetOption( NULL, INTERNET_OPTION_REFRESH , NULL, 0 );
  145. }
  146. }

这段代码中想特别说明的有2个地方,

1. 关于Option数组的使用,一开始的时候,是直接使用数字做为索引的,即Option[0],Option[1], Option[2], ..., 这样做当然可以工作。不过稍微隔几天,你就会发现记不清各个索引的函义了,哪个是放代理的,哪个是放URL的,那个是放bypass的等,特别不利于后面代码的维护。经过思考之后,还是觉得使用有意义的枚举名称更合适,看了WinInet.h定义的几个值,可以直接用做索引,于是将代码稍微修改,变成了上面的样子。Option[0]是占位用的,真正的有意义的Option是从Option[1]开始。这样一来,不管是在函数开头的初始化的部分,还是函数中间给个别选项的赋值,都显得特别清楚。Magic Number不见了。将这种方式和原来的方式做个简单的对比:

a. 设计都在编码之前,在写代码之前,大都会有一个思路,Option[0]放什么,Option[1]放什么,可是由于使用数字做索引,代码类似这样

  1. Option[1].dwOption = INTERNET_PER_CONN_FLAGS;
  2. Option[1].Value.dwValue = PROXY_TYPE_DIRECT;

或许开始时心里知道1代表着什么,但真正看代码,从代码的角度分析,却是反过来,是从右边的值来推导左边变量的含义的,这违反了编码的基本原则,有时使人困惑。即使Option[1]你在设计时并不打算放INTERNET_PER_CONN_FLAGS,从代码是看不出来的,相反,如果语句象下面这样,

  1. Option[INTERNET_PER_CONN_FLAGS].dwOption = INTERNET_PER_CONN_PROXY_SERVER;

你很快会发现其中的错误,因为左边右边不匹配。

b. a中开始良好的编码在后面也能体现出来优势,DebugOutputOption( Option ); 输出了Option数组的值,如果使用数字做索引,你还能清楚Option[0], Option[1]代表什么,它们的类型会是什么嘛?你需要惊人的记忆力。使用有意义的枚举名做为索引,则可以轻松帮助你实现这个功能,事半功倍。你很容易就能确定哪个是DWORD,哪个是字符串。

  1. Option[INTERNET_PER_CONN_FLAGS].Value.dwValue

  2. Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue

2. JustForOutputOption是一个local class,真正的起作用的代码就一条语句,local class的定义见http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/topic/com.ibm.xlcpp8a.doc/language/ref/cplr062.htm,这里为什么要用Local Class呢?最初,在查询浏览器的代理设置之后和开始进行新的代理设置之前,就是直接打印调试信息的语句,当时就想虽然就是一句调试输出的代码,但这句代码比较复杂,而且重复了2次,有必要封装一下。不过,当时因为偷懒,并没有做。后来因为1的原因,Option数组中的顺序变了,不得不修改这2句调试语句,挺麻烦的,于是乎又有了包装的念头。首先想到的肯定是用一个独立的函数包装,想了一下放弃了,这个地方使用函数包装并不合适。

a. 如果使用一个单独的函数,函数的范围至少得是类的成员函数,但是却只在这个函数内部使用,

b. 假设后面有其它函数会使用这个函数,函数的参数应该如何设计?传递Option类型的指针和数组大小嘛,那么Option数组的顺序呢?无法确保其它的函数的Option数组成员的顺序。

所以,从范围来看,作用域仅限于这个函数内部,没有必要影响整个类,甚至全局。从功能设计来看,也不适合设计函数,因为不通用。对熟悉C的开发者来说,在这个地方,宏是个选择,就用来作简单的文字替换即可。对于C++的开发者来说,Local class是个更好的选择,毕竟宏有许多缺点。因此,最终使用local class来包装这个调试输出语句,并且重载了operator(),使用起来象函数调用一样方便。

  1. DebugOutputOption( Option );

Local class还有其它的用途,比如RAII等等。顺便提下,C++是一个备受争议的语言,很多其它语言的拥趸讥讽C++含有大量无用的特征,Local Class正是其中之一。下这些人。

上面2点说得差不多了,最后补充一下,代码是写得玩的,未经QA测试,不排除有bug的可能性。

 

 

阅读(263) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
评论热议
posted on 2016-01-25 16:30  玄冬  阅读(407)  评论(0编辑  收藏  举报