研读公司代码

1.理解业务,用技术解决具体的问题

我们现在所做的事情,不是儿戏、不是演习、不是科学实验
我们现在所做的事情,都是针对实际在发生的问题
我们所做的事情,都在左右着事情的发展
我们不是纯粹的软件公司,我们生产软件的目的是为了“服务业务”
不了解所针对的问题,怎么可能把问题的解决方法找到,找好?
如果我们的工作不能解决问题,而是不断的带来新的问题,这样的工作结果很难给人带来成就感、带来快乐
所以,第一步:主动去理解我们所从事的业务
而不是,等待别人给你解释这个问题怎么分析、怎么解决
这是一个思考和解决问题的机会
不要把自己仅仅定位为一个"程序员Programmer",你是”工程师Engineer“
锻炼你自己,成长你自己

2.参数有效性检查,确保数据一定合法可用

Be a good function by verifying your parameter
每个函数的参数都有“有效取值范围"
不要信任函数的输入参数,不要让“别人BUG的熊熊烈火顺道把你家也烧了,拒敌以国门之外"
通过“参数有效性检测”,当遇到你无法接受的参数时,就应该提出抗议(Exception/result False) / 拒绝服务等
除非你的函数协议中包括了“异常参数下使用默认值”的协议等
永远不要信任函数的输入,尤其Public和API接口函数。

'''
下面是一个”计算博主收益“例子,$createive['profitMargin']是利润率
'''
if($creative['profitMargin'] > 0){
    $user_click_money = $creative['unit_price']*(1-$creative['profitMargin']);
}else{
    $user_click_money = $creative['unit_price']*DEFAULT_PROFIT_MARGIN_RATIO;
}
'''
上述code只检查了利润率是否大于0,如果大于0,就按照正常比率计算;否则就按照“默认比率DEFAULT_PROFIT_MARGIN_RATIO”计算。
问题是$createive['profitMargin']的合理范围应该是[0,1]之间,如果利润率大于1,那么结果是很诡异的,博主结算有问题。
如下code对利润率做了更好的检查,提高了程序的鲁棒性Robustness
利润率
'''
if($creative['profitMargin'] >= 0 and $creative['profitMargin'] <= 1){
    $user_click_money = $creative['unit_price']*(1-$creative['profitMargin']);
}else{
    $user_click_money = $creative['unit_price']*DEFAULT_PROFIT_MARGIN_RATIO;
}
疑问:你也许会说,如果每个函数都做有效性检测,那么检测的工作很有可能会重复,不是会降低代码的效率么?下面是一些建议。
1.和代码的健壮性和安全性相比,效率的牺牲有那么重要么?
    a.相信,参数有效性检测这点点东西,对于现在的硬件系统来说,算不了什么
    b.特殊情况除外,下面会提到这个特殊情况(留个悬念⎋)
2.你的代码是否有一致的参数检查规则,或者你的代码是否声明了对“参数有效性检查的要求”
    a.“谁受益、谁负责”:由函数调用者负责参数的有效性检测,可以在函数调用前,在函数外统一实施;函数内部不做有效性检测i.请在函数声明处指名:你是否需要调用者在函数外部做参数有效性检测
    b.“信任自己人”:API接口和Public的函数参数一般来自外部输入,通常需要在API接口函数和Public函数开始坐有效性检测;这里,你很难约束别人会输入什么,因为你是开门做生意的
    c.“攘之外,安于内”:public函数做参数检查,private函数不做参数检查
        i.前提:你要区分好publicprivate函数的定义
        ii.只要public参数正确,public函数的内部处理逻辑正确,private函数不会受到不合法的参数
        iii.减少了重复检验的代价
3.请在coding style或者函数声明中,指出你对参数有效性检测的要求

3.例外多发地,一定要捕获


Catch exceptions where they always show upEdit
在程序中,难免会遇到需要依赖外部资源的情况
这些资源往往是不可靠、易出错的,例如在外部API调用、磁盘IO、网络、数据库等操作等地方。
在这些地方,往往需要实施对异常情况的捕获
因为:
1.你需要知道发生了什么事情:成功、失败、警告;
  a.你需要至少为相应的情况进行记录,以为将来的BUG查找提供依据
  b.也许没有捕获异常,会导致你的代码陷入不可预知的轮回
2.处理例外:要看错误的类型,是你是否有能力处理?
  a.并非唯一依赖的外部资源:也许你只需要记录一个warning甚至info级别的log,然后用默认值来进行计算
  b.唯一依赖的外部资源,并且你有权决定下一步怎么办:记录error log,做出你的决定
  c.你没有决定权的错误类型:写一个error或者exception的log,然后把例外抛出去,让它的领导去操心


'''
下面用一个数据抓取的例子来说明这个问题:
数据抓取函数中的例外处理
'''
private function get_weibo_user_info($weibo_type, $weibo_id){
    if($weibo_type == WT_SINA)
        return SinaWeiboSDK::getUser($weibo_id);
    else if($weibo_type == WT_TENCENT)
        return TencentWeiboSDK::getUer($weibo_id);
    else
        return array(DO_CODE=>RET_ERROR, DO_MSG=>"Not supported weibo type");
}
'''
平台通过HTTP Request调用这个函数,但是我们都知道,SinaWeiboSDK不仅仅是一个网络调用,而且还收到APPKEY的频率限制,所以很容易出错、各种异常会出现。
出现异常以后,HTTP Request将接受不到任何内容,只能坐等超时,因此他们不知道应该如下进行下一步的处理。
这个问题比较好的方法是:加入TRY-CATCH,让函数总是返回“可读Reable”的数值;一切尽在掌握。
数据抓取函数中的例外处理
private function get_weibo_user_info($weibo_type, $weibo_id){
    try{
        if($weibo_type == WT_SINA)
            return SinaWeiboSDK::getUser($weibo_id);
        else if($weibo_type == WT_TENCENT)
            return TencentWeiboSDK::getUer($weibo_id);
        else
            return array(DO_CODE=>RET_ERROR, DO_MSG=>"Not supported weibo type");
    }catch(Exception $ex){
        Logger.exception("Exception when ......, msg:". $ex.getMessage())
        return array(DO_CODE=>RET_ERROR, 
                             DO_MSG=>"WeiboSDK API Error or remote function call timeout.",
                 ERR_CODE=>'API-10001');
    }
}
'''

不要随意处理你无力解决的例外Edit
Never try to solve an exception that you wouldn't be able to handleEdit
不要在你不能handle这个Exception的时候
简单写一个try-catch
然后在catch里面写一个log就跑了
这是很不负责任的coding style,必将受到人民的唾弃
因为实在是没人知道发生了什么事情、事情有多么的严重、应该如何应对
信息不完全造成的错误操作或者漏操作,将导致连锁反应,将系统带入一个未知的状态
数据库操作是大家经常要做的事情。一般大家会通过“工厂模式”来创建和维护数据库连接。然而数据库链接是脆弱、稀缺的资源,数据库连接异常是经常发生的事情。正如下面所示:

'''
数据库链接异常
'''
@staticmethod
def create_connection(host, port, db, user, passwd, use_unicode=None, charset=None, connect_timeout=None):
    try:
        new_connection = mysql.connector.connect(   host=host, port=port,
                                                    db=db, user=user,
                                                    passwd=passwd, use_unicode=use_unicode,
                                                    charset=charset, connect_timeout=connect_timeout
                                                )
        info_msg = "OK making MySQL Connection"
        LogHelper.debug(MysqlConnManager.__name__, info_msg)
        LogHelper.info(MysqlConnManager.__name__, info_msg)
        return new_connection
    except mysql.connector.Error as mysql_err:
        error_msg = ("Error when making connection to mysql server: " +
                                    mysql_err.msg + "_" + str(mysql_err.errno))
        LogHelper.debug(MysqlConnManager.__name__,error_msg)
        LogHelper.exception(MysqlConnManager.__name__,error_msg)
        LogHelper.error(MysqlConnManager.__name__,error_msg)
        return None
'''
上面的代码片段,在晕倒MySQL服务器链接错误时,做了Log记录,然后做了一个return None的动作。
而调用这个函数的人,只有在开始使用这个数据库链接对象是,才会发现创建失败。发现的时候,可能很多不可逆的操作已经做完了。
因此,相对更稳妥的做法是:将遇到的mysql连接异常抛出去,让函数调用者来解决问题;因为create_connection本身对于链接错误的异常是无力处理的。
数据库链接异常
'''
@staticmethod
def create_connection(host, port, db, user, passwd, use_unicode=None, charset=None, connect_timeout=None):
    try:
        new_connection = mysql.connector.connect(   host=host, port=port,
                                                    db=db, user=user,
                                                    passwd=passwd, use_unicode=use_unicode,
                                                    charset=charset, connect_timeout=connect_timeout
                                                )
        info_msg = "OK making MySQL Connection"
        LogHelper.debug(MysqlConnManager.__name__, info_msg)
        LogHelper.info(MysqlConnManager.__name__, info_msg)
        return new_connection
    except mysql.connector.Error as mysql_err:
        error_msg = ("Error when making connection to mysql server: " +
                                    mysql_err.msg + "_" + str(mysql_err.errno))
        LogHelper.debug(MysqlConnManager.__name__,error_msg)
        LogHelper.exception(MysqlConnManager.__name__,error_msg)
        LogHelper.error(MysqlConnManager.__name__,error_msg)
        raise mysql_err

另外一些情况下,你try...catch到例外之后,发现自己”摊上事了“,而且是”你无法解决的大事“,这个时候就要:
1.如果你还有上级领导,即上层call你的函数,你就应该把异常抛出,让他决定;
2.如果你已经是最高领袖了,那么你就应该
a.记录Exception或者Error log
b.给"有关人士state-holder"通知:邮件或者短信
c.退出执行程序
d.收到通知后,一般需要人肉处理这些问题,例如数据库连接、网络异常的;

4.不要做太多的假设


No more 'it maybe, I wishes', etc.
不要假设输入会怎样,不要假设用户一天发微博多少条;
这种假设会让你的程序无法应对突发情况;
"墨菲定律"告诉我们,异常情况总是会出现在你不想他出现的时机和地方
不要让你的程序只能处理正常的情况
不要做太多的"假设",就像你不能信任你的函数输入一样
不要假设API调用传给你的任务单价一定大于0;
不要假设用户在一个计费周期内只会发送小于100条原创微博;
只要你提供了批量接口,就不要假设修改短链状态时,每次提交的短链数量在你或者HTTP可以容纳的范围之内;
不要假设你的服务器,和服务提供方之间的网络状况会一直很好;
不要假设数据库连接在你第一次链接成功后,会一直保持联通的状态;例如Redis, MysqlConnection等等
不做过多的假设,让你的程序可以应对各种正常和突发的情况。这种情况还很多,例如使用单例模式连接数据源时,上一个时刻连接还成功的Redis服务器,可能现在连就有可能出现问题

/*
 * 连接redis Redis getInstance 
 */
public static function getInstance($redisConfig=null)
{
    if (empty(self::$instance))
    {
        if (empty($redisConfig))
        {
            return null;
        }
        $Redis = new Redis();
        $Redis->connect($redisConfig['host'], $redisConfig['port'],$redisConfig['timeout']);
        $ping = $Redis->ping();
        if(!$ping){
            throw new Exception('redis is not connect');
        }
        $Redis->setOption(Redis::OPT_PREFIX, $redisConfig['cache_id_prefix']);
        self::$instance = $Redis;
    }
    return self::$instance;
} 
/* 
上述代码中 if(empty(self::$instance))的检测是不够的,因为你创建的不是持久连接;哪怕是持久连接,也有可能因为网络问题,导致self::$instance处于异常的状态。
所以:假设$instance非空则Redis连接可用,这是不成立。即使非空的$instance,也需要通过ping检测才能知道是否存在异常。
当然,如果你的代码可以在发现异常情况下,通过“重新连接”等方式,解决这些异常,那么恭喜你,你的代码Robustness提升了
 */
/*
 * 连接redis Redis getInstance
 */
public static function getInstance($redisConfig=null){    
    $triedtimes = 0;
    while($triedtimes <= MAX_TRYING_TIMES){
        if (empty(self::$instance)){
            if (empty($redisConfig)){
                throw new Exception('Failed to connect Redis because config is empty');
            }
            $Redis = new Redis();
            $Redis->connect($redisConfig['host'], $redisConfig['port'],$redisConfig['timeout']);
            self::$instance = $Redis;
        }    
        $ping = self::$instance->ping();
        if(!$ping){
            if($tryiedtiems > MAX_TRYING_TIMES)
                throw new Exception('redis is not connect');
            else
                Logger.warning("Retring Redis conneciton $triedtimes....");
        }else{
            self::$instance->setOption(Redis::OPT_PREFIX, $redisConfig['cache_id_prefix']);
            break;
        }   
    } 
    return self::$instance;
} 

5.学会偷懒,会写巧代码


Be smart and write lean codesEdit
学会偷懒和节俭;不要用一成不变的代码解决所有问题;
偷懒不是不做,而是要巧做:即不重做、少做等等
节俭是“不假设资源是无限的,能少花一点是一点”
例如,在大部分情况下,可以用A方法可以大大减小代码执行的代价;其他特殊情况,用暴力的B方法去解决;
如果有这种可能,就应该写两个函数A+B
在合适的时候,调用适当方法
不要对自己的代码惜字如金,别写完了就舍不得改
拙笨、臃肿的代码不会长寿

猜你喜欢

转载自blog.csdn.net/qq1226317595/article/details/80374980