C++代碼的維護,從一個執行個體說起

來源:互聯網
上載者:User

最初發表在QQ空間,見:C++代碼的維護,從執行個體說起

小問題有大智慧-Proxy 伺服器的監測 是幾個月前的文章,最近碰到別人問如何設定代理的問題,又回顧了部分代碼,雖然時間不長,還是有不少記不清了。,於是就整理了那個設定代理的函數,代碼是實踐的科學,每寫一次,都會有點心得。

先把代碼貼出來,這個函數的大概流程是,先查詢當前的瀏覽器設定,然後根據使用者的設定,再決定

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.dwValueOption[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的可能性。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.