• 实践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) |
    给主人留下些什么吧!~~
    评论热议
  • 相关阅读:
    cocos2dx打包apk
    cocos2d 小游戏
    排序算法笔记二
    把一张合成图分拆出各个小图
    出栈入栈动画demo
    Android 面試題
    AS项目删减打包-01
    c程序指针题
    ubuntu14.04 设置默认登录用户为root
    Ubuntu14.04 Java环境变量配置
  • 原文地址:https://www.cnblogs.com/black/p/5171871.html
Copyright © 2020-2023  润新知