220 likes | 443 Views
微型重构-解决代码嵌套问题. 1000COPY 真懂得,讲出来 1000COPY@GMAIL.COM. 故事 1. 我的飞碟铸件 2. 一次做对?. 引入: 1. 评审,少量编码。 分销项目组一个月,第一次并组开发 2. 微观优化 if else,for ,while的深度嵌套 3. 真实例子 4. 力荐一种适合我们的方法==》 干掉嵌套 模式1 . 异常+函数==》if嵌套 模式2 优先序判断函数 ==> 多层简单嵌套 模式3 递归 ==> 多层for/while嵌套. 问题
E N D
微型重构-解决代码嵌套问题 1000COPY 真懂得,讲出来 1000COPY@GMAIL.COM
故事 1. 我的飞碟铸件 2. 一次做对?
引入: 1. 评审,少量编码。分销项目组一个月,第一次并组开发 2. 微观优化 if else,for ,while的深度嵌套 3. 真实例子 4. 力荐一种适合我们的方法==》 干掉嵌套 模式1. 异常+函数==》if嵌套 模式2 优先序判断函数 ==> 多层简单嵌套 模式3 递归 ==> 多层for/while嵌套
问题 1. TDD,敏捷,CMMI,RUP,OO? 2. 回到基础来
Winberg 咨询师《理解专业程序员》 “评审了几十家机构的几百份程序,所有的程序都体现了大致相同的问题” 编码问题的第一条就是代码的分支和嵌套问题。 我们的很多代码常常一个函数动辄上千行,不断的嵌套,形成一个巨大的锯齿形代码,让阅读测试都成为一个问题。这个问题依然存在。 设计问题。提到代码中对输入参数进行合法性检查; 我们的代码很多根本没有考虑这个问题。 便利特性。即使评估了当年两个教授的公开出版的样本代码,也有很多新近 引入的便利特性--可以让代码更加容易阅读,更高效率的特性--没有使用。 这本书应该写于1970's, 。我想如果我们有机会去做这样的一个评审,从统计学上看应该结果雷同。
问题:删除草稿的代码 DEMO . if (ReadSysCon(syscon_DraftDelTs)) and ( not isBatchAuditing) then //需要给出询问提示 Begin if SuperMessageBox(pchar('是否删除草稿?'), '提示', mbtInformation, mbbOKCancel) = idok then begin if (CheckPowerNoDialog('0902','038')) then //检查是否有删除草稿权限 begin if (BillOperatorID<>CurrOperatorID) then //操作员是否为制单人 begin if (CheckPowerNoDialog('0902','078')) then //是否有删除他人草稿权限 begin ExecuteProcByName('F_D_DeleteDly', ['@dbname', '@vchcode'], ['bakdly', o_indexid], nil); ExecuteProcByName('F_D_DeleteDlyndx', ['@vchcode'], [o_indexid], nil); SendMessageToAllWindow(WM_SETTLEDCOMPLETE, lParam(StringToInt(o_indexid)), 0); end else Begin Application.MessageBox('你没有单据中心业务草稿的删除他人草稿的权限,无法删除!','提示',48); end; end else begin ExecuteProcByName('F_D_DeleteDly', ['@dbname', '@vchcode'], ['bakdly', o_indexid], nil); ExecuteProcByName('F_D_DeleteDlyndx', ['@vchcode'], [o_indexid], nil); SendMessageToAllWindow(WM_SETTLEDCOMPLETE, lParam(StringToInt(o_indexid)), 0); end; end else Begin Application.MessageBox('你没有单据中心业务草稿的删除的权限,无法删除!','提示',48); end; end; end 1. 5层嵌套。不容易阅读,逻辑复杂 2. 很多这样的代码 3. 32行
1. if return ,先判定不符合条件的,让它退出 refactor : replace nested loop with guard statement . M. Fowler 2. supermessage ->confirm 3. 共同逻辑合并 4. 魔术数字 5. 单条语句不必用begin end块 6. 两个messagebox完全相同,可以合并 7. 条件不符的根本不必提示“是否删除” 8. 第二个if和第三个if可以合并 9. 第一个if可以not,直接退出,这样减少一个嵌套 10. 两个数据库操作应该用事务
老张提供的例子 if then 几公里的代码 else 几公里的相同代码,略微做修改 end 不易阅读 不好维护
改了后: if (ReadSysCon(syscon_DraftDelTs)) and ( not isBatchAuditing) then Begin if SuperMessageBox(pchar('是否删除草稿?'), '提示', mbtInformation, mbbOKCancel) = idok then begin checkError(!CheckPowerNoDialog('0902','038'),'你没有单据中心业务草稿的删除的权限,无法删除!") checkError(BillOperatorID<>CurrOperatorID && !CheckPowerNoDialog('0902','078'),'你没有单据中心业务草稿的删除他人草稿的权限,无法删除!') ExecuteProcByName('F_D_DeleteDly', ['@dbname', '@vchcode'], ['bakdly', o_indexid], nil); ExecuteProcByName('F_D_DeleteDlyndx', ['@vchcode'], [o_indexid], nil); SendMessageToAllWindow(WM_SETTLEDCOMPLETE, lParam(StringToInt(o_indexid)), 0); end; 1. 2层vs 5层嵌套,好多了 2. 9行 vs 32行 还可以继续 SuperMessageBox()
方法 1. 利用异常的特性,化嵌套为直线代码 这里在checkerror内使用了abort异常的特性。 Function checkError(cond,msg) If (cond) begin ShowMessage(msg);abort;end;end; 通过利用异常的特性,把嵌套代码变成直线代码。 2. 抽取公用代码
More 1. 除掉魔术数字和魔术字符串。比如这样的'0902','078',都是魔术字符串。应该用枚举和常量替代。 2. 执行存储过程需要加入事务。
在一个例子:同样的问题和同样的方法 if (r == -301) throw new Exception( string.Format( "该{0}【{1}】存在经营历程或已经在职员信息中使用,不能分类!", "部门", parFullName)); else if (r == -501) throw new Exception(string.Format(UsedError, "订单")); else if (r == -502) throw new Exception(string.Format(UsedError, "借欠单")); else if (r == -503) throw new Exception(string.Format(UsedError, "维修单")); else throw new Exception(string.Format("操作失败!返回码{0}", r));
同样的问题和同样的方法 CheckError(r == -301,"该{0}【{1}】存在经营历程或已经在职员信息中使用,不能分类!",typeName,parFullName); CheckError(r == -501, UsedError, "订单"); CheckError(r == -502, UsedError, "借欠单"); CheckError(r == -503, UsedError, "维修单");
适用性 只要有异常处理就可以采用 vb on error js try catch c# try catch delphi try catch 写一个checkError的函数。checkError(bool cond,msg)
使用历史 easycmd 366club 分销 erp5.0 尝试分析服装版
模式2 - 优先序判断函数 ==> 多层嵌套 if ( pricetrack.toString() == "1"){ //使用价格跟踪 if (trackprice){//有跟踪价 rowData.price = trackprice; rowData.taxprice = rowData.price * (1 + rowData.tax/100); this._round(rowData,"taxprice",4); } else{//没有跟踪价 if (selectedData.recprice){//有最近进价 rowData.price = selectedData.recprice; rowData.taxprice = selectedData.recprice * (1 + rowData.tax/100); this._round(rowData,"taxprice",4); } else{//没有最近进价 if (selectedData.preprice5){ //有预设进价 rowData.price = selectedData.preprice5; rowData.taxprice = selectedData.preprice5 * (1 + rowData.tax/100); this._round(rowData,"taxprice",4); } else{ //没有预设进价 rowData.price = ""; rowData.taxprice = ""; } } } }
模式2 - 优先序判断函数 ==> 多层嵌套 if ( pricetrack.toString() == "1"){ rowData.price = getFirstNotNull([tracePrice,selectedData.recprice,selectedData.preprice5,""]); if (rowData.price!="")){ rowData.taxprice = rowData.price * (1 + rowData.tax/100); this._round(rowData,"taxprice",4); } } 2层 vs 4层 引入函数,好读
模式3 - 利用递归 ==> 多层嵌套 函数功能:删除数组中.sEname中为空的元素。 procedure TfrmCommonFilter.DecEmptyItem; var i,j,k,l: Integer; begin k := 0; for I:= Low(ArrFilter) to High(ArrFilter) do begin if ArrFilter[i].sEName = '' then begin l := 0; while ArrFilter[i].sEName = '' do begin if l + i >= High(ArrFilter) then begin if l > k then k := l; Break; end; Inc(l); for j := i to High(ArrFilter) - 1 do begin ArrFilter[j].sEName := ArrFilter[j + 1].sEName; ArrFilter[j].sCName := ArrFilter[j + 1].sCName; ArrFilter[j].sItemType := ArrFilter[j + 1].sItemType; ArrFilter[j].iFlag := ArrFilter[j + 1].iFlag; end; ArrFilter[High(ArrFilter)].sEName := ''; end; end; end; SetLength(ArrFilter,High(ArrFilter) - k ); end;
模式3 - 利用递归 ==> 多层for,while嵌套 函数功能:删除数组中.sEname中为空的元素。 DeleteEmpty(ArrFilter,0); function DeleteEmpty(ArrFilter,FromIndex) begin var index = FindFirstEmpty(ArrFilter,FormIndex); if (index <> -1 ) begin MoveForward(ArrFilter,index) DeleteEmpty(ArrFilter,index) End end function MoveForward(ArrFilter,to) begin for j := from to High(ArrFilter) - 1 do begin ArrFilter[j].sEName := ArrFilter[j + 1].sEName; ArrFilter[j].sCName := ArrFilter[j + 1].sCName; ArrFilter[j].sItemType := ArrFilter[j + 1].sItemType; Arrer[j].iFlag := ArrFilter[j + 1].iFlag; end; end 1层嵌套 vs 4层嵌套 3层循环 vs 无循环 ;递归特别适合解决嵌套循环的代码。可以得到比较神奇的效果。遇到这一情况,试试递归。 30 vs 14 行
模式3 - 利用递归 ==> 多层嵌套 衍生效果: 对于需要删除的数据结构,尽量不要选择数组。如果是链表,一次循环即可。
重点推荐 模式1
重点推荐 模式1 重点推荐 模式1 Edsger Wybe Dijkstra 这个娃 1. structure programming 2. goto statement considered harmful 3. The Humble Programmer 程序员必须知道谦逊,方能 在九个数量级的复杂度上有所成就