程序中中的注釋,一般是有益處的,可以知曉程序的一些邏輯說明,或是參數解釋。但是有些程序,因為注釋太多,反而引起維護上的不方便,刪掉了怕以后不能出現問題不好查找原因,不刪除留在代碼中,對程序的維護人員,是一種痛苦。
以下列舉我可以理解的的原因,供分析參考。
1 方法調用移動到新的類型中,原方法仍然保留在原來的類型中
//public void ExecuteSqlCommand(string sqlCommandText) //{ //this.ExecuteSqlCommand(sqlCommandText, CommandType.Text, null); //} ......
ExecuteSqlCommand方法已經被移植到新的輔助類型SqlHelper中,但是這里的還沒有直接刪除。保留的目的,有可能存在反射調用,在報錯之后,看到這里的代碼被注釋后,再才會重新查找這片代碼的新的歸屬。
2 刪除不需要考慮的的條件或情況,因為怕考慮不充分而沒有刪除代碼
static ClientProxyFactory() { _managerTypeAssemblies = new List<string>(); _managerTypesCache = new Dictionary<string, Type>(); _managerInstancesCache = new Dictionary<string, IManagerBase>(); EnableManagerInstanceCache = true; //if (Platform == CommunicationPlatform.Local) //{ // foreach (string file in ManagerAssembly) // { // if (File.Exists(String.Format("{0}.dll", file))) // { // Assembly assembly = Assembly.Load(file); // _managerTypeAssemblies.Add(assembly); // } // } //} } ......
在上面的代碼中,CommunicationPlatform為Local的情況被注釋掉了,但是沒有直接刪除。被注釋的代碼的作用是添加程序集到_managerTypeAssemblies類型中。可能是的原因是,系統現在不再支持Local模式,而只支持.net Remoting模式,所以這段代碼會被注釋。
3 因為考慮不周全,導致代碼中注釋與功能並存。保留注釋是為了出錯的情況下,幫助分析代碼
來看下面的二個公共方法的定義,一個是反射調用靜態方法,另一個是反射調用靜態屬性的值。
/// <summary> /// 靜態方法的調用 /// </summary> /// <param name="file"></param> /// <param name="typeName"></param> /// <param name="methodName"></param> /// <returns></returns> public static object InvokeStaticMethod(Type typeName,string methodName,object [] args) { //Assembly assembly = Assembly.LoadFile(file); //Type type = assembly.GetType(typeName); Assembly assembly = typeName.Assembly; //obj2 = Activator.CreateInstance(type, args); System.Reflection.MethodInfo method = typeName.GetMethod(methodName,new Type[]{ typeof(object)}); // object obj= assembly.CreateInstance(typeName); // object obj = Activator.CreateInstance(typeName, args); return typeName.InvokeMember(methodName, BindingFlags.Public | BindingFlags.Static, null, null, args); } public static object GetStaticPropertyValue(Type type, string propertyName) { object objec=CreateObjectInstance(type); PropertyInfo propertyInfo = type.GetProperty(propertyName,BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly); //type.GetProperty(propertyName).GetValue(null, null); return propertyInfo.GetValue(objec, null); }
從上面被注釋的代碼中可以看到,需要的代碼與被注釋的代碼共同存在。可能因為參數或是條件的不同,被注釋的代碼可以運行,是正確的,但是當前情況下,沒有被注釋的代碼才可以運行。公共框架的開發本身要考慮的條件很多,測試也要充分才能保證無錯。從這里也可以看出,反射給代碼重構帶來障礙,因為不知道代碼在哪里會被反射調用,所以代碼只有等到運行出錯之后才發現。
除非正常調用情況下無法實現,應該減少反射調用代碼。或者對反射調用代碼進行封裝,所有的反射調用放在一個ReflectionHelper類型中,如果要查找問題,只需要在這個類型的相應方法中打斷點即可。
4 異常處理機制的改變,導致代碼中捕獲異常的代碼被注釋
請看下面的二個方法,用於拷貝文件和拷貝目錄
//bakup file public static BackupFile(string sourceFileName, string destFileName) { try { System.IO.File.Copy(sourceFileName, destFileName, true); return true; } catch (Exception e) { throw e; } } public static void CopyDirectory(string oldDir, string newDir) { try { DirectoryInfo dInfo = new DirectoryInfo(oldDir); CopyDirInfo(dInfo, oldDir, newDir); } catch (Exception exc) { throw new Exception(exc.ToString()); } }
這種依靠返回true/false的得知代碼是否執行成功。我現在比較反感這樣的代碼。因為如果拷貝文件或拷貝目錄出錯,沒有報錯,異常在這里被捕獲。而且第二個方法CopyDirectory中,拋出異常后會改變異常的堆棧信息,這樣導致比較難發現錯誤。如果是WinForms程序,應該以下面的方式處理異常
CustomExceptionHandler eh = new CustomExceptionHandler(); AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CustomExceptionHandler.CurrentDomain_UnhandledException); Application.ThreadException += new ThreadExceptionEventHandler(eh.OnThreadException);
在程序的入口處,設置UnhandledException 和ThreadException 的處理情況,程序中有發生異常后,流程會跳轉到這里,作統一的處理。以我的實踐,像下面這樣的方法,不應該這樣處理
/// <summary> /// 復制文件,如果目標文件已經存在則覆蓋掉 /// </summary> /// <param name="oldFile">源文件</param> /// <param name="newFile">目標文件</param> public static void CopyFile(string oldFile, string newFile) { try { File.Copy(oldFile, newFile, true); } catch (Exception exc) { throw new Exception(exc.ToString()); } }
而修改的異常捕獲策略之后,代碼像這樣,也不友好
/// <summary> /// 復制文件,如果目標文件已經存在則覆蓋掉 /// </summary> /// <param name="oldFile">源文件</param> /// <param name="newFile">目標文件</param> public static void CopyFile(string oldFile, string newFile) { //try //{ File.Copy(oldFile, newFile, true); //} // catch (Exception exc) //{ // throw new Exception(exc.ToString()); //} }
應該直接去掉這個方法封裝,直接在代碼中調用File.Copy。
5 .NET框架的發展,導致一些代碼變成多余但又沒有刪掉,先將其注釋
動態構造SELECT語句的字段列時,在構造完成后,通常會給它們加上逗號
SELECT ITEM_NO ,ITEM_GROUP FROM GBITEM
通常我們會用ArrayList或是IList<string> 把ITEM_NO和ITEM_GROUP聚集在一起,再循環一次,給每個字符的末尾增加一個逗號,最后去掉多余的逗號:
public static string ArrayToList(string[] ids, string separativeSign) { int num = 0; string str = string.Empty; foreach (string str2 in ids) { num++; string str3 = str; str = str3 + separativeSign + str2 + separativeSign + ","; } if (num == 0) { return ""; } return str.TrimEnd(new char[] { ',' }); }
MSDN中字符串類型string的Join方法,可以達到這個目的,只需要調用Join方法即可。MSDN中有例子解釋如下
如果 separator 為“,”且 value 的元素為“apple”、“orange”、“grape”和“pear”,則 Join(separator, value) 返回“apple, orange, grape, pear”。 如果 separator 為 nullNothingnullptrnull 引用(在 Visual Basic 中為 Nothing),則改用空字符串 (Empty)。
6 運行環境的改變,注釋掉代碼以便於以后發現問題
請參考下面的方法例子
/// <summary> /// 獲取一個文件的絕對路徑(適用於WEB應用程序) /// </summary> /// <param name="filePath">文件路徑</param> /// <returns>string</returns> public static string GetRealFile(string filePath) { string strResult = ""; //strResult = ((file.IndexOf(@":\") > 0 || file.IndexOf(":/") > 0) ? file : System.Web.HttpContext.Current.Server.MapPath(System.Web.HttpContext.Current.Request.ApplicationPath + "/" + file)); strResult = ((filePath.IndexOf(":\\") > 0) ? filePath : System.Web.HttpContext.Current.Server.MapPath(filePath)); return strResult; }
這個方法之前可能是用Web環境中,現在改成WinForms或是控制台項目中,導致被注釋的代碼會報錯,於是將它注釋。關於路徑的選擇,AppDomain的BaseDirectory或是Application.ExecutePath都是獨立於運行環境的(ASP.NET,Console,WinForms,Windows Services),應該優先考慮使用。
7 測試數據以注釋的方式,保留中代碼中,增加對代碼的解釋
string host = System.Configuration.ConfigurationManager.AppSettings["EmailHost"]; MailMessage m = new MailMessage(); m.Subject = subject; m.SubjectEncoding = Encoding.UTF8; m.From = new MailAddress(from); m.To.Add(to); m.Body = body; m.BodyEncoding = Encoding.UTF8; m.IsBodyHtml = true; SmtpClient client = new SmtpClient(); client.Host = host; //"ASHKGEX4.asia.ad.flextronics.com"; client.Credentials = new System.Net.NetworkCredential("asia\baoshhli", ""); client.Port = 25; client.DeliveryMethod = SmtpDeliveryMethod.Network; client.UseDefaultCredentials = false; client.Send(m);
如上面代碼中的host=ASHKGEX4.asia.ad.flextronics.com,作者測試問題時是用這個host也沒有報錯,於是將這個數據保留在代碼中,以方便以后代碼維護人員測試問題。
8 對每一個數據項都進行注釋,必要的注釋和不必要的注釋混雜在一起
例如下面的代碼
DataTable Dt = new DataTable(); DataRow Dr; Dt.Columns.Add("name");//名稱 Dt.Columns.Add("type");//類型:1為文件夾,2為文件 Dt.Columns.Add("size");//文件大小,如果是文件夾則置空 Dt.Columns.Add("content_type");//文件MIME類型,如果是文件夾則置空 Dt.Columns.Add("createTime");//創建時間 Dt.Columns.Add("lastWriteTime");//最后修改時間
Add方法后面的對字段的解釋,有的是是多余的。有的是必要的。
我以為,多余的注釋是:名稱,創建時間 ,最后修改時間 這三列的值可以通過代碼或是它本身的名字得知。
type這一列的注釋,我以為這是很有必要的,這可以減少維護人員的工作量。