PHP 板


LINE

看板 PHP  RSS
常常觉得,软体总是越写越复杂,过一段时间我一定维护不了。 甚至怀疑自己到底够不够格当一个软体工程师。 直到有一天我学会了两招。 今天想跟各位分享我写程式以来,学到最重要的两招。 哪里有写错的话,希望各位不吝指教。 http://blog.turn.tw/?p=83 ---------------------------------------------------------------- 以前写project的时候,常常越写越不安。 心里头总是有两个声音: 为了更能应付不同状况,程式码要处理的情况越来越多、越来越复杂。if/else跟 switch/case越写越多层、一层又在一层又在一层里面,越写越像在挑战自己智商的极限。 这个project再继续维护(扩充功能、修bug)三个月没问题、半年还撑得住, 之後一定越写越慢。不到一年,程式码将庞杂到难以挽救。 总觉得哪里出了问题:如果之後写商用软体、有持续扩充的野心, 这样写code下去一定会遇到大麻烦。莫非要跟客户或是老板说: 这project我无法继续维护了,因为我能力不够、您可能要另请高明。 难道我真的智商不足,没能力写出规模比较大的软体? 终於,有一天,我学到了两招,从此不再有这种担忧。 今天,就来跟各位分享我的秘密。 (天阿,口气真夸张,好像要推销产品还是卖保险,哈) 这两招是Guard Clauses与EAFP原则(It’s Easier to Ask Forgiveness than Permission )。下面跟各位分享,有写错的部份也期待各位前辈指教 Guard Clauses 与Guard Clauses相对的是Nested Structure。 下面这种code是典型的Nested Structure,相信也曾出现在你的project。灾难一场。 function the_method($some_variable){ if (some_expression){ // some code // ... if (another_expression){ // some more code // ... if (another_expression){ // 这里是理想情况 // 终於可以在这里做想做的事了 // 不过我相信这串code过了一个月连你自己都看不懂 } } // ... maybe some 'else' block to handle here } // ... and here } 遇到这种情况,要怎麽应用guard clauses?请看下面的范例。 以电子商务网站为例,下面是某class建立一张购物订单的method。要输入单品的id阵列、 买家id、付款方式与折扣金额: function create_one($ids, $user_id, $payment_method, $deduction_amount) { // 抵扣金额不是整数 // 呼叫这个method的工程师一定哪边搞错了吧 if ( !is_integer( $deduction_amount) ){ throw new Exception("invalid 'deduction_amount': $deduction_amount. it's not integer."); } // 之前决定资料库内用负数代表折扣金额 // 传正数进来的工程师你昨天很晚睡吼 if ($deduction_amount > 0){ throw new Exception("invalid deduction amount: $deduction_amount. it can't be position"); } // 资料库内找不到这个$user_id // 呼叫这个method的工程师你要不要再检查一下程式码 $query = $this->db ->where('id',$user_id) ->get('users'); if ( $query->num_rows() == 0 ){ throw new Exception("invalid user id: $user_id. the user doesn't exist"); } // 公司只有接受三种付款方式 $acceptable_payment_method = array( 'ATM', 'at_home', 'store'); if ( !in_array( $payment_method, $acceptable_payment_method ) ) { throw new Exception("invalid payment_method: $payment_method ."); } // 继续一串要检查的项目 // 这边终於可以把资料整理一下然後存进资料库了 // 可能还是会在这里喷出不知道什麽鬼的exception // 但整段code真的好读、好维护、又robust很多了 return true; }// end function create_one 後者是不是清楚非常多? EAFP原则(It’s Easier to Ask Forgiveness than Permission) *注1 与EAFP相对的是LBYL原则(Look Before You Leap) 想像下面这个用LBYL写的除法函式: function safe_divide_1($x, $y){ if ($y == 0){ echo "分母为零"; return null; } else{ return $x/$y; } } 接着是用EAFP写的除法函式: function safe_divide_2($x, $y){ try{ return x/y } // 如果你有定义这样一个ZeroDivisionException类别的话才行 catch (ZeroDivisionException $e){ echo "分母为零"; return null; // 或是再throw $e给上面的stack处理也行 } // 就算你上面没定义ZeroDivisionException // 这边还是会抓得到错误然後显示讯息啦 catch (Exception $e){ echo ($e->getMessage()); return null; } } 後者有没有比较乾净俐落? (好吧,这边sample code看起来好像没有,哈哈) LBYL代表你小心处理各种情况;EAFP代表你直接执行最重要的程式码,然後碰到问题 (出现exception)再面对。 在MVC web开发领域,通常就是controller写try/catch去执行model的某些method(EAFP) ,而不要在controller内自己写一大堆判断式检查(LBYL)。 对应到刚刚电子商务订单的例子,某个成立订单的controller根据EAFP会写出 这样的code: try{ $this->Order_model->create_one($ids, $user_id, $payment_method, $deduction_amount); } catch (Exception $e){ // 这样写其实很混,使用者会看到一个很丑的error页面 // 但是work就是了 exit($e->getMessage()); } // some code ... // 显示恭喜使用者的页面/请他拿钱包准备付款的页面之类的 若是根据LBYL原则去写controller,则会写出一大串if/else if/else去检查$ids, $user_id, $payment_method, $deduction_amount这几个变数、 小心处理完各种情况才去呼叫create_one这个method。程式码会又长又丑, 看不懂整段code重点在哪。 以上是个人学习写code学到最重要的其中两招。其中有写错的部份也期待各位前辈指教。 Q&A Q1: guard clauses看起来超直觉的啊,为什麽我到今天才知道可以这样写code? 这跟学校教育有关。正统教育有个名词叫做single entry/exit point,说是为了维持code 的高品质,叫你应该这麽做。 Q2: 有时候我还是觉得写一串if/else比guard clause直觉啊? 程式码的外观其实直接说明了某些事情。guard clauses的code看起来就像在说:如果发生 这件事,赶快处理完、然後滚吧。一串if/else if/else像是在说:你看,各个情况都一样 重要、所以大家都在一样深层的巢状结构里面, 真正重要的code跟某些狗屁情况一样重要,所以他们一样在这恐怖程式码的内部第N层! 到底该怎麽写还是看实际情况。不妨都试试,看当下哪个适合? Q3: 我就老实告诉你吧!我根本不想学exception或是try/catch是什麽!因为我的code用 if/else或是switch/case/default就把全部情况都处理好了! 会这样想是因为你以前学C语言对吗?C语言没有exception机制,工程师必须写出完整处理 各种情况的程式码。毕竟LBYL(Look Before You Leap )也是一种撰写风格。那就写到你 觉得痛苦的那天再回来学这几招吧。 丢exception不但code较易读,还能在原本function之外去处理相关错误(把问题丢给「其 他层级」去处理,像是model丢给其他model、model再丢给controller之类的)、追踪 exception在各stack内的流动。 Q4: 好吧,我刚是骗你的。其实我对正在用的语言的exception机制不熟。我还能用guard clauses吗? 可以!上面sample code的throw new Exception全部写成return false即可。 然後EAFP就不会写成try/catch了。 以同一个建立订单的例子来说,如果你觉得成功跟失败都是人之常情,请写成这样: $success =$this->Order_model->create_one($ids, $user_id, $payment_method, $deduction_amount); if ($success){ // some code ... // 显示恭喜使用者的页面/请他拿钱包准备付款的页面之类的 } else{ echo "亲爱的使用者,因为我还没学exception所以我不能告诉你哪边出错,反正出错 了。" } 如果你觉得在你系统内应该不太会失败才对,请写成这样: $success =$this->Order_model->create_one($ids, $user_id, $payment_method, $deduction_amount); if ( !$success){ echo "亲爱的使用者,因为我还没学exception所以我不能告诉你哪边出错,反正出错 了。" } // some code ... // 显示恭喜使用者的页面/请他拿钱包准备付款的页面之类的 Q5: 你Q2、Q3好像在说有时候的确需要写一串丑丑if/else if?可以再做一点说明? if/else if 可以想成在处理「合理的各种情况」。它们都一样重要,所以code在同层级各 占了一整串。 throw exception则是在处理「不合理但确实可能面对的情况」。这种情况随便丢个 exception还是秀error之类的,叫它快滚就好了。 如果你很在意user experience,那就去客制化(宣告class去继承原生exception)各个 exception、帮助其他developer(通常就是你自己)在各种exception之下处理各种情况吧 。 不过,在Web开发领域,喷exception常是在controller呼叫model函式之时发生。我通常直 接在view内用JavaScript提示使用者。 他若是进行什麽诡异操作跳过了我的JavaScript协助, 那我就用浏览器大方的喷个超丑的error message页面给他。 Q6: 为什麽Q4你要示范两种写法?你让我好困扰、我到底要挑哪种? 把Q2再看一遍。 ————————————————————————————– 注1:EAFP与LBYL这两个词我是在Python社群看到的。这篇用字遣词可能不甚精确。 注2:文章里建议的某些处理方式非常随便。以上仅是我目前的理解。请不断追寻自己最喜 欢的best practice。 --



※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 118.160.253.56
1F:→ poopoo888888:希望能得到前辈们的意见,谢谢。 03/23 16:32
2F:推 fri13:之前看过用函式拆开流程的 比较不会一堆if或catch 03/23 17:48
3F:→ fri13:更正 单一函式看起来比较不会一堆if或catch 03/23 17:49
4F:推 wayway2004:good 03/23 21:53
5F:推 GoalBased:你第一个例子会比较清楚 是因为有注解吧= = 03/23 22:41
6F:推 tkdmaf:我能建议的就2楼说的,函式单一职责吧! 03/23 23:25
7F:推 Kenqr:推 03/24 12:45
8F:推 locklose:包装相关且相依,切割延伸与额外 04/02 15:50
9F:→ locklose:刚出道我老板讲的很简单,但我摔了一堆坑才明白… 04/02 15:52







like.gif 您可能会有兴趣的文章
icon.png[问题/行为] 猫晚上进房间会不会有憋尿问题
icon.pngRe: [闲聊] 选了错误的女孩成为魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一张
icon.png[心得] EMS高领长版毛衣.墨小楼MC1002
icon.png[分享] 丹龙隔热纸GE55+33+22
icon.png[问题] 清洗洗衣机
icon.png[寻物] 窗台下的空间
icon.png[闲聊] 双极の女神1 木魔爵
icon.png[售车] 新竹 1997 march 1297cc 白色 四门
icon.png[讨论] 能从照片感受到摄影者心情吗
icon.png[狂贺] 贺贺贺贺 贺!岛村卯月!总选举NO.1
icon.png[难过] 羡慕白皮肤的女生
icon.png阅读文章
icon.png[黑特]
icon.png[问题] SBK S1安装於安全帽位置
icon.png[分享] 旧woo100绝版开箱!!
icon.pngRe: [无言] 关於小包卫生纸
icon.png[开箱] E5-2683V3 RX480Strix 快睿C1 简单测试
icon.png[心得] 苍の海贼龙 地狱 执行者16PT
icon.png[售车] 1999年Virage iO 1.8EXi
icon.png[心得] 挑战33 LV10 狮子座pt solo
icon.png[闲聊] 手把手教你不被桶之新手主购教学
icon.png[分享] Civic Type R 量产版官方照无预警流出
icon.png[售车] Golf 4 2.0 银色 自排
icon.png[出售] Graco提篮汽座(有底座)2000元诚可议
icon.png[问题] 请问补牙材质掉了还能再补吗?(台中半年内
icon.png[问题] 44th 单曲 生写竟然都给重复的啊啊!
icon.png[心得] 华南红卡/icash 核卡
icon.png[问题] 拔牙矫正这样正常吗
icon.png[赠送] 老莫高业 初业 102年版
icon.png[情报] 三大行动支付 本季掀战火
icon.png[宝宝] 博客来Amos水蜡笔5/1特价五折
icon.pngRe: [心得] 新鲜人一些面试分享
icon.png[心得] 苍の海贼龙 地狱 麒麟25PT
icon.pngRe: [闲聊] (君の名は。雷慎入) 君名二创漫画翻译
icon.pngRe: [闲聊] OGN中场影片:失踪人口局 (英文字幕)
icon.png[问题] 台湾大哥大4G讯号差
icon.png[出售] [全国]全新千寻侘草LED灯, 水草

请输入看板名称,例如:WOW站内搜寻

TOP