您的位置:首页 > 编程语言 > C语言/C++

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

2016-01-25 16:30 886 查看
最初发表在QQ空间里,http://user.qzone.qq.com/31731705/blog/1309416291,正好有征文大赛,就放在这里吧。


 

[align=center][/align]

小问题有大智慧-代理服务器的监测 是几个月前的文章,最近碰到别人问如何设置代理的问题,又回顾了部分代码,虽然时间不长,还是有不少记不清了。

,于是就整理了那个设置代理的函数,代码是实践的科学,每写一次,都会有点心得。


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

void CWRSBar::ModifySetting( const Option::ProxyEntryInfo &pei )

{

// refer to following value in WinInet.h

// so as to use these values as index in array.

// :)

/*

#define INTERNET_PER_CONN_FLAGS 1

#define INTERNET_PER_CONN_PROXY_SERVER 2

#define INTERNET_PER_CONN_PROXY_BYPASS 3

#define INTERNET_PER_CONN_AUTOCONFIG_URL 4

#define INTERNET_PER_CONN_AUTODISCOVERY_FLAGS 5

*/

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

INTERNET_PER_CONN_OPTION_LIST List;

INTERNET_PER_CONN_OPTION Option[6];

unsigned long nSize = sizeof(List);

Option[0].dwOption = 0;

Option[0].Value.dwValue = 0;

// connection flags

Option[INTERNET_PER_CONN_FLAGS].dwOption = INTERNET_PER_CONN_FLAGS;

Option[INTERNET_PER_CONN_FLAGS].Value.dwValue = PROXY_TYPE_DIRECT;

//|PROXY_TYPE_AUTO_DETECT;

// proxy server

Option[INTERNET_PER_CONN_PROXY_SERVER].dwOption = INTERNET_PER_CONN_PROXY_SERVER;

Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue = NULL;

// proxy bypass

Option[INTERNET_PER_CONN_PROXY_BYPASS].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;

Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue = NULL;

// auto config URL

Option[INTERNET_PER_CONN_AUTOCONFIG_URL].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;

Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue = NULL;

// others ...

Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS;

Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS].Value.dwValue = AUTO_PROXY_FLAG_USER_SET | AUTO_PROXY_FLAG_DETECTION_RUN;

List.dwSize = nSize; //sizeof(INTERNET_PER_CONN_OPTION_LIST);

List.pszConnection = NULL;

List.dwOptionCount = 5;

List.dwOptionError = 0;

List.pOptions = &Option[1];

// Use it like C macro

class JustForOutputOption

{

public:

void operator()( INTERNET_PER_CONN_OPTION *p )

{

ATLASSERT( p );

INTERNET_PER_CONN_OPTION *Option = p;

WRST( LOG_TREND_PROXY )(

TEXT("Option[INTERNET_PER_CONN_FLAGS](0x%x): (0x%x), ")

TEXT("Option[INTERNET_PER_CONN_PROXY_SERVER](0x%x): (%s), ")

TEXT("Option[INTERNET_PER_CONN_PROXY_BYPASS](0x%x): (%s), ")

TEXT("Option[INTERNET_PER_CONN_AUTOCONFIG_URL](0x%x): (%s), ")

TEXT("Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS](0x%x): (0x%x)."),

Option[INTERNET_PER_CONN_FLAGS].dwOption, Option[INTERNET_PER_CONN_FLAGS].Value.dwValue,

Option[INTERNET_PER_CONN_PROXY_SERVER].dwOption,

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

Option[INTERNET_PER_CONN_PROXY_BYPASS].dwOption,

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

Option[INTERNET_PER_CONN_AUTOCONFIG_URL].dwOption,

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

Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS].dwOption, Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS].Value.dwValue

);

}

}DebugOutputOption;

// 查询当前的设置.

if( InternetQueryOption( NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize ) )

{

DebugOutputOption( Option );

int proxyType = Option::MANUAL;

std::tstring proxyAuto, proxyDirect;

g_wrsCfg.GetOpt( WRS_TRENDPROXY_AUTO_NAME, proxyAuto );

g_wrsCfg.GetOpt( WRS_TRENDPROXY_DIRECT_NAME, proxyDirect );

if( pei.name == proxyAuto )

proxyType = Option::AUTO;

if( pei.name == proxyDirect )

proxyType = Option::DIRECT;

std::Bit32 flag;

enum { USE_OLD_PAC = 1, USE_OLD_BYPASS, USE_OLD_PROXY, };

flag[USE_OLD_PAC] = flag[USE_OLD_PROXY] = flag[USE_OLD_BYPASS] = true;

TCHAR proxy[BufSize], bypass[BufSize];

proxy[0] = bypass[0] = 0;

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

switch( proxyType )

{

// 使用自动配置脚本,如果原来有值,使用原来的值。

case Option::AUTO:

{

Option[INTERNET_PER_CONN_FLAGS].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_AUTO_PROXY_URL;

if( !Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue || !lstrlen( Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue ) )

{

// use AUTO value now

//Option[0].Value.pszValue = const_cast<LPTSTR>( pei.server.c_str() );

Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue = const_cast<LPTSTR>( g_wrsTrendProxyCfg.autoURL.c_str() );

flag[USE_OLD_PAC] = false;

}

}

break;

// 直连

case Option::DIRECT:

{

Option[INTERNET_PER_CONN_FLAGS].Value.dwValue = PROXY_TYPE_DIRECT;

}

break;

// 使用用户配置的代理

case Option::MANUAL:

{

Option[INTERNET_PER_CONN_FLAGS].Value.dwValue = PROXY_TYPE_DIRECT|PROXY_TYPE_PROXY;

if( Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue )

GlobalFree( Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue );

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

ATLASSERT( lstrlen( proxy ) );

Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue = proxy;

flag[USE_OLD_PROXY] = false;

if( Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue )

GlobalFree( Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue );

lstrcat( bypass, g_wrsTrendProxyCfg.bypass.c_str() );

if( lstrlen( bypass ) )

lstrcat( bypass, TEXT(";") );

lstrcat( bypass, TEXT("<local>") );

Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue = bypass;

flag[USE_OLD_BYPASS] = false;

}

break;

}

DebugOutputOption( Option );

// 设置代理选项

InternetSetOption( NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, nSize );

// free memeory

if( Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue && flag[USE_OLD_BYPASS] )

GlobalFree( Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue );

if( Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue && flag[USE_OLD_PROXY] )

GlobalFree( Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue );

if( Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue && flag[USE_OLD_PAC] )

GlobalFree( Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue );

// system

// Notifies the system that the registry settings have been changed so that it verifies the settings on the next call to InternetConnect.

// This is used by InternetSetOption.

InternetSetOption( NULL, INTERNET_OPTION_SETTINGS_CHANGED, NULL, 0 );

// ie

// Causes the proxy data to be reread from the registry for a handle. No buffer is required.

// This option can be used on the HINTERNET handle returned by InternetOpen. It is used by InternetSetOption.

// so if we don't call this API ie might show previous value even after last API call.

//

//InternetSetOption( NULL, INTERNET_OPTION_REFRESH , NULL, 0 );

}

}

这段代码中想特别说明的有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]放什么,可是由于使用数字做索引,代码类似这样

Option[1].dwOption = INTERNET_PER_CONN_FLAGS;

Option[1].Value.dwValue = PROXY_TYPE_DIRECT;

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

Option[INTERNET_PER_CONN_FLAGS].dwOption = INTERNET_PER_CONN_PROXY_SERVER;

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

b. a中开始良好的编码在后面也能体现出来优势,DebugOutputOption( Option ); 输出了Option数组的值,如果使用数字做索引,你还能清楚Option[0], Option[1]代表什么,它们的类型会是什么嘛?你需要惊人的记忆力。

使用有意义的枚举名做为索引,则可以轻松帮助你实现这个功能,事半功倍。你很容易就能确定哪个是DWORD,哪个是字符串。

Option[INTERNET_PER_CONN_FLAGS].Value.dwValue

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(),使用起来象函数调用一样方便。

DebugOutputOption( Option );

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

下这些人。

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




 

 


<script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script>

阅读(263) | 评论(0) | 转发(0) |

0
上一篇:编写安全代码:数组和指针的本质以及何时不能互换

下一篇:我的C++技巧总结

相关热门文章
A sample .exrc file for vi e...

游标的特征

IBM System p5 服务器 HACMP ...

busybox的httpd使用CGI脚本(Bu...

Solaris PowerTOP 1.0 发布

linux dhcp peizhi roc

关于Unix文件的软链接

求教这个命令什么意思,我是新...

sed -e "/grep/d" 是什么意思...

谁能够帮我解决LINUX 2.6 10...

给主人留下些什么吧!~~

评论热议
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: