《Clean Code》 代码简洁之道

小编介绍

原文小编: 罗Bert C. 马丁, Object
Mentor公司主任,面向对象设计、格局、UML、敏捷方法学和终端编程领域的老牌顾问,是《敏捷软件开发:原则、方式、与实施》的小编。
翻译小编:韩磊,互连网产品与营业专家,技术书籍著译者。译著有《梦断代码》和《C#编程风格》等。(竟然不是程序员~~~)

许是不可名状的经历多了,一旦熬过,你便会越发羞于争议生活。

情节大约

本书后几章重点讲了java相关的类、系统、和产出的宏图介绍,较简单,与简短之道不是特意融合,故而简约,想要详细摸底的提出去看更优质的事无巨细讲解。
本书首要站在代码的可读性上谈论。可读性? 顾名思义,代码读起来简单易懂,
让人心境愉悦,大加表彰。
在N年之后,自己仍然客人照旧可以稍加阅读就能了解其中的意思。什么是卫生代码?看看程序员鼻祖们怎么说的,

  1. 洁净代码只做好一件事。—— Bjarne Stroustrup, C++语言发明者

  2. 清洁代码从不隐藏设计者的企图。—— Grady Booch,
    《面向对象分析与布置》小编

不可能想着,那代码我能看懂就好了,
即使当下能看懂,那个月甚至一年之后吧。更不可能说为了展现自己编程“高大上”,故意用部分不为人知的语法,如

const LIMIT = 10
const LIMIT = Number(++[[]][+[]]+[+[]])
  1. 尽可能少的借助关系,模块提供尽量少的API。—— 戴夫 Thoms, OTI创办人,
    Eclipse战略教父
  2. 代码应该经过其字面表达含义,命名和情节保持一致。 —— MichaelFeathers, 《修改代码的办法》小编
  3. 裁减重复代码,甚至从不再一次代码。—— Ron Jeffries,
    《C#顶点编程探险》小编
  4. 让编程语言像是专门为焚薮而田那么些标题而存在。 —— 沃德 Counningham,
    Wiki发明者

​​

图形选自网络

有意义的命名

  • 名副其实

好的变量、函数或类的名称应当早就答复了所有的大难题,假设须要注释补充,就不算名副其实。
工具函数内部的暂时变量可以稍微能接收。

// what's the meaning of the 'd'?
int d;
// what's list ?
List<int []> list;

int daysSinceCreation
int daysSinceModification

  

那里的重定向,起名为“redirection”会不会更好一些,

/**
 * 重定向
 */
public function forward(Request $request, $controller, $action) {}
/**
 * 重定向
 */
public function default(Request $request,  $controller, $action) {}

  

既是登记帐号,为什么不直接命名为 register
呢?也许会说,注册就是增创帐号,create也是增创帐号,自然,create可以表示注册。可新增帐号可能是和谐注册,也恐怕是系统分配,还可能是协会者新增帐号,业务场景不平等,完结也很可能不均等。所以,提出取一个明明的,有意义的,铁画银钩函数干了吗的称谓。

//注册账号
public function create($data) {}

  

  • 避免误导

程序员必须防止留下掩藏代码本意的谬误线索。变量命名包蕴数据类型单词(array/list/num/number/str/string/obj/Object)时,需确保该变量一定是该类型,包含变量函数中或许的改动。更致命的误导是命名和情节意义差异,甚至完全相反。

// 确定是 List?
accountList = 0
// 确定是 Number?
nodeNum = '1'

//确定所有情况返回值都是list吗?
function getUserList (status) {
    if (!status) return false
    let userList = []
    ...
    return userList
}
.align-left {
  text-align: "right";
}

  

  • 做有意义的不相同

product/productIno/productData 怎样区分?哪个代表哪个意思? Info 和
Data就如 a / an / the
一样,是意思含糊的废话。如下函数命名也是向来不意思的界别,

getActiveAccount()
getActiveAccounts()
getActiveAccountInfo()

  

  • 应用读的出来的称谓

读不出来就不便利纪念,不便宜调换。大脑中有很大一块地点用来拍卖语言,不行使起来有些浪费了。

  • 应用可搜索的名号

让IDE协助自己更便捷的开支。借使在公私措施里面起个变量名叫value,全局搜索,然后一脸懵逼地看着那许多条搜索结果。
(value vs districts)

  • 每个概念对应一个词

传媒资源叫media resources 依然 publisher?

  • 充分有含义的语境

firstName/lastName/street/city/state/hourseNumber
=>
addrFirstName/addrLastName/addrStreet/addrCity/addrState/hourseNumber

01

不久前跟闺密聊天,又有了不谋而合的共鸣,——开首没有根由的,懒得生气了。真是又懒出了新高度。

即使如此如此多年直接抱有默契,但那不是大家这时候初识的榜样。那时自己说自己像猫,总是抱着祥和的狐狸尾巴一刻不停地原地打转,乐此不疲。她说她像驴,蒙着眼绕磨盘走,只为近在嘴边却永远也吃不到的胡萝卜。十年前她总计自身“落落寡合”,想来至今未变,可知我是一个令人一眼从头顶发丝看穿鞋底厚度的人,也仍旧是因为,她就住在自己的某部心室里;而自我,却难以给她定义,她壮硕却伊人,踏实又飘落,坚贞不屈却多变,热烈又忧郁,她的各类阶段都有很备受关注标样子却偏偏给自己起名“茫小然”,她自许为重度恐怖症病者。就是那样三个八竿子打不着的人,在当年的高校高校里成了一道只有大家才懂的风景线。

近期,分别从分裂地点辗转到那座城池的我们,即使住得只隔一条街,但生活轨迹完全两样,一年也罕见一回会合,但平日相聚,却一向看不出是好久不见的故交。我曾感慨那种明显路程很近走起来却很远的心急火燎,但他说,好在我们仍可以相约在各自的窗前看同样轮明月。我默许,甚至柳暗花明,觉得这脑洞开得绝妙无比。

前日她发现自家很自嗨的玩起了公众号,然后敲过来几行字:“曾忆年少,大家都能迷恋汪国真,课桌里都装过陈喆外婆。时光飞逝,我的膀子都熬成了汤,而这么些具有隐世无争性格的小可人,在自己的神气世界里修建了一道高高的围墙。那里真美。”其实在我看来,大家还都仍旧十年前的指南。她,仍像驴一样,为生活不断理想化而极力向上拼搏着;而自我,还似当初那只猫,将完美生活化,没头脑的自寻其乐。

     或者,那就是大白天径直在生存里苟且的大家在夜深人静时乐享的一刻欢悦。要是你也同等,为了生存或可以走得太快感觉累了,或者你怕自己走得太慢而莫名惶恐了,都不妨,停下来歇歇脚,在人流拥挤中,暂时丢掉那多少个耀眼甚至老大的光环;在时刻静好中,学会欣赏那多少个了然而不刺眼的忧思。


注释

如何也比不上放置优异的诠释来的得力。
怎样也不会比杂乱无章的笺注更有本事搞乱一个模块。
怎么着也不会比陈旧、提供错误音信的诠释更有破坏性。
若编程语言丰盛有表明力,或者大家善于用那几个语言来表明意图,就不那么要求注释——也向来不要求。

  • 小编为何极力贬低注释?

申明会撒谎。由于程序员不可以持之以恒保险注释,其设有的年月越久,离其所描述的代码越远,甚至最终可能完全错误。不规范的诠释比尚未注释坏的多,净瞎说,真实只在一处地方存在:代码。

  • 申明的万分用法是弥补大家在用代码表明意图时遭到的破产。

    // 禁用、解冻
    public function option(Request $request) {}
    // 记录操作日志
    protected function writeLog($optType,$optObjectName, $optId, $optAction) {}

  

=>

protected function recordOperationLog($optType,$optObjectName, $optId, $optAction) {}

  

将方面的 注释 + 代码 合成下方纯代码,望着更简明,且不会读不懂。
再者,可以在函数定义的地点添加表达性注释,可不可能在每个用到那么些函数的地点也增长注释,那样,在读书函数调用的条件时,还得翻到定义的地方瞅瞅是什么样意思。但只要函数本身的称号就能描述其意义,就不设有这些标题了。
别担心名字太长,能精确描述函数本身的含义才是更主要的。

  • 诠释无法美化不好的代码。
    对此烂透的代码,最好的点子不是写点儿注释,而是把它弄干净。与其花时间整一大堆注释,不如花时间整好代码,用代码来阐释。

    // check the obj can be modified
    if (obj.flag || obj.status === ‘EFFECTIVE’ && user.info.menu === 1) {

    // todo
    

    }
    if (theObjCanBeModified()) {}
    function theObjCanBeModified () {}

  

02

80后的大家,是全国首推的率先代独生子女,人称“蜜罐里的人”。但有目共睹,我的幼时,生活品质并不高。

█零岁的时候,我在举国严打超生游击队的风口上幸存,为此大叔被撤废了劳模称号,并列入社团黑名单剥夺提干权力一生,甚至在某段时期还为此丢了劳作,但他们尚无跟长大的自己抱怨过,只是简短一句:“是条命,就要大力让它活着”。

█ 十岁的时候,我喜爱和猫狗一起躺在院里仰望天空,看各色白云在蓝天涌动,而自己仿若插翅已投入那广博的怀中。

█二十岁的时候,我时长感到心中空虚,莫名泪湿眼底,会做种种美好的梦,同样,也有会在寒夜惊醒。

█三十岁后,我忽地窥见精神逐渐贫瘠,却遍寻不见恐惧。

算来已有近四年时间尚未碰摸过一本书了。新买的书未泰安已蒙了尘。在平凡琐碎的沸沸扬扬中,固然延续顶着一张冷漠脸却难掩内心的恐慌。反倒是两岁的小孩子平时吚吚哑哑,说着令她快乐的有所故事。有时,见我无言以对时,她会那多少个令人不安,关注地问我是还是不是还好。平常,要没有他提醒自己“我跟你开玩笑的。”我真猜不出她上一秒跟自身说的话原来是他用那幽微脑袋瓜精心为本人遍的耻笑。

想来太多的突发性,大家的心绪都坐上了过山车。每有生气,我身边的那团永远活力无限的小肉肉总能准确捕捉并飞速跑过来搂住自家,然后绵软地说“岳母慢悠悠的,我喜欢小姨连连慢悠悠的”或者献上一记轻吻,在自家耳边奶声奶气地说“丈母娘你麻烦了​,要自身协助吗?让自家来保安你!”无论此刻你在做哪些,也不管她有没有错。此刻,我情愿分分钟被暖化。并对刚刚涌上来的拥有不开玩笑说,走好,不送!
  
您看,连一个生活尚不可以自理能力的“小屁孩”都如此温暖,你还天天自寻那么多烦忧,摆张臭脸给什么人看?收起你的臭脸,给协调一个微笑,给身边人一个微笑,世界也会还你一个微笑。
    
若干年前,曾有人对自家说:“你年轻的身躯里一定住着一位智慧的前辈,他,安宁,祥和”。当时自我被吓坏了。

近年来,我想说,许是玄而又玄的阅历多了,一旦熬过,你便会尤其羞于争议生活。

好注释

  1. 少于商厦代码规范必要写的法度相关心释。

/**
 * Laravel IDE Helper Generator
 *
 * @author    Barry vd. Heuvel <barryvdh@gmail.com>
 * @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      https://github.com/barryvdh/laravel-ide-helper
 */

namespace Barryvdh\LaravelIdeHelper;

  

  2. 对打算的诠释,如,

function testConcurrentAddWidgets() {
...
// this is our best attempt to get a race condition
// by creating large number of threads.
for (int i = 0; i < 25000; i++) {
 // to handle thread
}
}

  3. 阐释
  有时,对于一些不能改变的标准库,使用注释把一些晦涩难懂的参数或重回值的含义翻译为某种可读的花样,也会是立见成效的。

function compareTest () {
  // bb > ba
  assertTrue(bb.compareTo(ba) === 1) 
  // bb = ba
  assertTrue(bb.compareTo(ba) === 0) 
  // bb < ba
  assertTrue(bb.compareTo(ba) === -1) 
}
// could not find susan in students.
students.indexOf('susan') === -1

  

  4. 警示
  注释用于警示其余程序员某种后果,也是被支持的。

  函数,

// Don't run unless you have some time to kill
function _testWithReallyBigFile () {}

  文件顶部注释,

/**
 * 文件来内容源于E:\Git_Workplace\tui\examples\views\components\color\tinyColor.js,需要新增/编辑/删除内容请更改源文件。
 */

  5. TODO

  来不及做的,使用TODO举行诠释。即便那些被允许存在,但不是最最书写TODO的理由,必要定期清理。

  6. 放大

  注释可以用来放大某些望着不创制代码的重要。

  不就是个trim()么?

// the trim is real importan. It removes the starting
// spaces that could casuse the item to be recoginized
// as another list
String listItemContent = match.group(3).trim()

  

  没引入任何编译后的js和css,代码如何健康办事的吧?请看注释。

<body>
  <div id="app"></div>
  <!-- built files will be auto injected -->
</body>

  

  7. 公共API中的DOC
  公共文档的doc一般会用来自动生成API协助文档,试想即使一个公共库没有API表明文档,得是一件多么苦痛的事务,啃源码开支时间莫过于太长。

 

坏注释

  1. 喃喃自语
    写了有的除了自己外人都看不懂的文字。

  2. 结余的注释
    不难易行的函数,尾部地点的笺注全属多余,读注释的年月比读代码的年月还长,完全没有任何实质性的效果。

    // Utility method that returns when this.closed is true.
    // Throws an exception if the timeout is reached.
    public synchronized void waitForClose(final long timeoutMillis)
    throw Exception {
    if (!closed) 
    {
     wait(timeoutMillis);
     if (!closed)
       throw new Exception("MockResponseSender could not be closed");
    }
    }
    

      

  3. 误导性注释
    代码为东,注释为西。

  4. 结余的注脚

  

// 创建
public function create(Request $request) {}
// 更新
public function update(Request $request) {}
// 查询
public function read(Request $request) {}
// 删除
public function delete(Request $request) {}

  

  $table已经开始化过了,@var string
这一行注释看上去如同就没那么需要了。

/**
 * The table name for the model.
 * @var string
 */
protected $table = 'order_t_creative';

  

  5. 括号前边的诠释

  只要根据函数只做一件事,尽可能地短小,就不要求如下代码所示的尾括号标记注释。

try {
  ...
  while () {
   ...
  } // while
  ...
} // try
catch () {
  ...
} // catch

  

  一般不在括号后方添加注释,代码和注释不混在一行。

function handleKeydown (e) {
  if (keyCode === 13) { // Enter
    e.preventDefault()
    if (this.focusIndex !== -1) {
      this.inputValue = this.options[this.focusIndex].value
    }
    this.hideMenu()
  }
  if (keyCode === 27) { // Esc
    e.preventDefault()
    this.hideMenu()
  }
  if (keyCode === 40) { // Down
    e.preventDefault()
    this.navigateOptions('next')
  }
  if (keyCode === 38) { // Up
    e.preventDefault()
    this.navigateOptions('prev')
  }
}

  

现作出如下调整,

function handleKeydown (e) {
  const Enter = 13
  const Esc = 27
  const Down = 40
  const Up = 38
  e.preventDefault()
  switch (keycode) {
    case Enter:
      if (this.focusIndex !== -1) {
        this.inputValue = this.options[this.focusIndex].value
      }
      this.hideMenu()
      break
    case Esc:
      this.hideMenu()
      break
    case Down:
      this.navigateOptions('next')
      break
    case Up:
      this.navigateOptions('prev')
      break
  }
}

  

  通过定义数字变量,不仅去掉了诠释,各类数字也有了温馨的意义,不再是魔法数字,依照代码环境,大约不会有人问,“27是什么看头?”
诸如此类的题材。再者,if情形过多,用switch代替,瞅着稍显简洁。最终,每一个都有执行了e.preventDefault(),可以放在switch外层,进行一遍书写。

  6. 名下和签署
  源码控制连串越发擅长记住什么人在曾几何时干了怎么着,没有要求添加签名。新品类方可免除地领悟该和什么人琢磨,但随着岁月的推迟,签名将更为不准确。
自然,那个也不比,支付宝小程序抄袭微信小程序事件的触及便是因为代码里面现身开发小哥的名字。假使为了版权需求,法律注解,我想写上小编也是未曾什么大难点的。

/**
 * Created by PhpStorm.
 * User: XXX
 * Date: 2017/9/29
 * Time: 14:14
 */

namespace App\Services;
use Cache;
class CacheService implements CacheServiceInterface
{
}
/**
 * 功能: 广告位管理
 * User: xxx@tencent.com
 * Date: 17-8-2
 * Time: 下午4:47
 */
class PlacementController extends BaseController
{
}

  

  7. 注释掉的代码
  直接把代码注释掉是讨厌的做法。Don’t do that!
其余人不敢删除注释掉的代码,可能会那样想,代码如故在当年,一定有其缘由,或者那段代码很关键,不可能去除。
其余人因为一些原因不敢删可以了解,但万一是团结写的诠释代码,有吗不敢删呢?再首要的声明代码,删掉后,还有代码控制种类啊,那几个系统会记住人为的每三遍变动,还操心吗啊?放心地删吧!管它什么人写的。

// $app->middleware([
//    App\Http\Middleware\DemoMiddleware::class
// ]);

// $app->routeMiddleware([
//     'auth' => App\Http\Middleware\Authenticate::class,
// ]);

if (APP_MODE == 'dev') {
    $app->register(Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class);
}
$app->register(\App\Providers\UserServiceProvider::class);
$app->register(\App\Providers\UserRoleServiceProvider::class);

  8. 音讯过多

  9. 别在诠释中增进有趣的历史性话题或毫无干系的底细描述。

  10. 注脚和代码没有掌握的牵连

  11. 诠释和代码之间的沟通相应醒目,倘若注释本身还索要解释,就太不好了。

/**
* start with an array that is big enough to hold all the pixels
* (plus filter biytes), and extra 200 bytes for header info
*/
this.pngBytes = new byte[((this.width + 1) + this.height * 3) + 200];

  

  12. 非公共代码的doc类注释

  有些doc类的诠释对公共API很有用,但假如代码不打算作公共用途,就一直不必要了。

上边的四行注释,除了第一行,其余的都展现很多余,无疑在再次函数参数已经描述过的始末。如果阅读代码的人花了时光看注释,结果什么也不曾,黯然;知道没用自行掠过,没有花时间看注释,那那注释还留着干啥。

/**
 * 根据媒体ID获取广告位ID
 * @param PlacementService $service
 * @param Request $request
 * @return Msg
 */
public function getPublisherPlacementIds(PlacementService $service, Request $request) {}

  

函数

  • 短小

函数第一规则是要短小,第二平整是还要更短小。if语句,else语句,while语句等,其中的代码块应该唯有一行。函数代码行指出不用超越20行,每行代码长度提议150个字符左右。如下代码片段,提议换行。

export default function checkPriv (store, path) {
  return store.state.user.privileges && (store.state.user.privileges.includes(path) || store.state.user.privileges.includes(`/${path.split('/')[1]}/*`) || isAll(store))
}

  

  • 函数应该只做一件事,做好那件事。

如下函数,executeSqlContent() 很明确不止做一件事,
前半有的完成了连接配置的得到,后半局地基于config执行sql。

/**
 * 根据文件名和文件路径执行具体的sql
 * @param $file
 * @param $dbConfigPath
 * @param $date
 */
protected function executeSqlContent($file, $dbConfigPath, $date)
{
    $config = [];
    // 获取数据库名称
    if ($file == 'nn_global.sql' || $file == 'nn_pub_template.sql') {
        // DB配置
        $config = config("database.connections.global");
        $userId = 'global';

    } elseif (strpos($file, 'nn_pub') !== false) {
        $fileName = explode('.', $file);

        $dbName = explode('_', $fileName[0]);
        if (count($dbName) == 3) {
            $dbInfo = UserDbTConfig::select(['onsn_name'])->where('dbn_name', $fileName[0])->first();
            if ($dbInfo) {
                $dbInfo = $dbInfo->toArray();
                $onsInfo = zkname($dbInfo['onsn_name']);
                $config = config("database.connections.individual");
                // 覆盖HOST
                $config['host'] = $onsInfo->ip;
                $config['port'] = $onsInfo->port;
                $userId = $dbName[2];
            }
        }
    }

    if ($config) {
        // sql语句
        $dbSqlConfig = file_get_contents($dbConfigPath . $file);
        if ($dbSqlConfig) {
            $this->info($file . '文件内容为:' . $dbSqlConfig);

            // 添加新的连接
            config(["database.connections.pp_pub_{$userId}" => $config]);
            $db = DB::connection("nn_pub_{$userId}");
            $db->statement($dbSqlConfig);

            // 执行成功,文件备份移动
            $dirName = 'static/bak/' . $date;
            if (!is_dir($dirName)) {
                mkdir($dirName, 0777, true);
            }
            $cmd = "mv " . $dbConfigPath . $file . "  " . $dirName;
            shell_exec($cmd);

            // 断开DB连接
            DB::disconnect("nn_pub_{$userId}");

            $this->info($file . '文件内容为执行完成');
        }
    }
}

  

  • 种种函数一个空洞层级,函数中混着不一致抽象层级往往简单令人迷惑。

  如下代码便是纸上谈兵层级不同, getConnectionConfig() ,属于已经悬空过的一层函数调用,下方的公文处理却是具体的落实。
举那几个事例只是为了求证分裂的抽象层级是那几个意思,由于函数本身不复杂,不设有令人迷惑的标题。
只是函数落成一旦混杂多了,不便于搞得精通某一行表明式是基础概念仍然细节,更加多的底细就会在函数中纠结起来。

protected function executeSqlContent($file, $dbConfigPath, $date)
{
    $config = $this->getConnectionConfig($file)
    if ($config) {
        // sql语句
        $dbSqlConfig = file_get_contents($dbConfigPath . $file);
        if ($dbSqlConfig) {
            $this->info($file . '文件内容为:' . $dbSqlConfig);

            // 添加新的连接
            config(["database.connections.pp_pub_{$userId}" => $config]);
            $db = DB::connection("nn_pub_{$userId}");
            $db->statement($dbSqlConfig);

            // 执行成功,文件备份移动
            $dirName = 'static/bak/' . $date;
            if (!is_dir($dirName)) {
                mkdir($dirName, 0777, true);
            }
            $cmd = "mv " . $dbConfigPath . $file . "  " . $dirName;
            shell_exec($cmd);

            // 断开DB连接
            DB::disconnect("nn_pub_{$userId}");

            $this->info($file . '文件内容为执行完成');
        }
    }
}

private function getConnectionConfig ($file)
{
    $config = []
    // 获取数据库名称
    if ($file == 'nn_global.sql' || $file == 'nn_pub_template.sql') {
        // DB配置
        $config = config("database.connections.global");
        $userId = 'global';
    } elseif (strpos($file, 'nn_pub') !== false) {
        $fileName = explode('.', $file);
        $dbName = explode('_', $fileName[0]);
        if (count($dbName) == 3) {
            $dbInfo = UserDbTConfig::select(['onsn_name'])->where('dbn_name', $fileName[0])->first();
            if ($dbInfo) {
                $dbInfo = $dbInfo->toArray();
                $onsInfo = zkname($dbInfo['onsn_name']);
                $config = config("database.connections.individual");
                // 覆盖HOST
                $config['host'] = $onsInfo->ip;
                $config['port'] = $onsInfo->port;
                $userId = $dbName[2];
            }
        }
    }
    return $config
}

  稍好一点的架空层级如下,当然excuteSql()还足以继承拆分,当书写函数的时候要求打空行来区分情节的多数时候
可以考虑拆分函数了。

protected function executeSqlByFile($file, $dbConfigPath, $date)
{
    if ($this->getConnectionConfig($file)) {
        $this->excuteSql($file, $dbConfigPath, $date)
    }
}

private function getConnectionConfig($file)
{
    $config = []
    // 获取数据库名称
    if ($file == 'nn_global.sql' || $file == 'nn_pub_template.sql') {
        // DB配置
        $config = config("database.connections.global");
        $userId = 'global';
    } elseif (strpos($file, 'nn_pub') !== false) {
        $fileName = explode('.', $file);
        $dbName = explode('_', $fileName[0]);
        if (count($dbName) == 3) {
            $dbInfo = UserDbTConfig::select(['onsn_name'])->where('dbn_name', $fileName[0])->first();
            if ($dbInfo) {
                $dbInfo = $dbInfo->toArray();
                $onsInfo = zkname($dbInfo['onsn_name']);
                $config = config("database.connections.individual");
                // 覆盖HOST
                $config['host'] = $onsInfo->ip;
                $config['port'] = $onsInfo->port;
                $userId = $dbName[2];
            }
        }
    }
    return $config
}

private function excuteSql($file, $dbConfigPath, $date)
{
    $dbSqlConfig = file_get_contents($dbConfigPath . $file);
    if ($dbSqlConfig) {
        $this->info($file . '文件内容为:' . $dbSqlConfig);

        config(["database.connections.nn_pub_{$userId}" => $config]);
        $db = DB::connection("nn_pub_{$userId}");
        $db->statement($dbSqlConfig);

        $dirName = 'static/bak/' . $date;
        if (!is_dir($dirName)) {
            mkdir($dirName, 0777, true);
        }
        $cmd = "mv " . $dbConfigPath . $file . "  " . $dirName;
        shell_exec($cmd);

        DB::disconnect("nn_pub_{$userId}");
        $this->info($file . '文件内容为执行完成');
    }
}
  • 应用描述性的函数名

  长而具有描述性的称号,比短而令人费解的称号好。(假使短也能,当然更好)
  长而颇具描述性的名号,比描述性长的笺注好。代码维护时,一大半程序员都会活动忽略掉注释,不可以确保每便变更都实时更新,越以后越不想看注释,因为很可能形成误导,程序才是真事实。
  所以,别怕长,更紧要的是描述性,看到那一个函数名称就精通是干啥的。读代码就像读英文小说一样,先干了吗,后干了吗,细节怎么干的?

  小窍门:可以利用IDE搜索扶助完善命名。

  即便结合文件名,publisherController,打死我也不可以将 all 和 移动媒体分类 联系起来。提议函数名:getMobileMediaClassification()

/**
 * 移动媒体分类
 */
public function all(PublisherServices $service, Request $request) {}

  

  完美命名示范,代码上方的表明或许已经不必要了,不过对此母语是华语的大家的话,就当是英文翻译了。

/**
 * 根据媒体ID获取广告位ID
 */
public function getPublisherPlacementIds(PlacementService $service, Request $request)

  

  • 函数参数

最了不起的参数数量是0,其次是一,再一次是二,应尽量防止三。除非有丰硕的理由,否则不要用多个以上的参数了。
参数多于多个,测试用例覆盖所有的也许值组合是令人生畏的。
幸免出现输出参数。

  • 标识参数。

起名,向函数传入布尔参数大约就是骇人听闻的做法,那样做,就是大声公布函数不止做一件事,为true会那样,为false会这样。非Boolean类型“标识”参数同理。

一般来说代码明确地提议initOrder举办了二种截然不相同的初阶化方式。

// 订单数据初始化分两种,一种为普通创建订单,一种为通过库存转下单
function initOrder(flag) {
  if (flag === true) {
    // normalInit
    // ...
  } else {
    // init order by inventory
    // ..
  }
}

  

改革如下,也许你会说,initOrder不照旧干了两件事儿呢?不,它不是投机干了那两件事儿,它只是背负叫别人干那两件事。
假若可以的话,initOrder中间的判定甚至能够放在能一贯获得flag的地方。

function initOrder(flag) {
  flag === true ? this.normalInit() : this.initOrderByInvenroty()
}

function normalInit () {
  // todo
}

function initOrderByInvenroty () {
  // todo
}

  

excuteSql($file, $dbConfigPath, $date) 中的参数 $dbConfigPath 和 $filefile_get_contents()的出力下成为了标识参数
$dbSqlConfig为真就实施主体函数,为假就不实施。

private function excuteSql($file, $dbConfigPath, $date)
{
    $dbSqlConfig = file_get_contents($dbConfigPath . $file);
    if ($dbSqlConfig) {
        $this->info($file . '文件内容为:' . $dbSqlConfig);

        config(["database.connections.pp_pub_{$userId}" => $config]);
        $db = DB::connection("nn_pub_{$userId}");
        $db->statement($dbSqlConfig);

        $dirName = 'static/bak/' . $date;
        if (!is_dir($dirName)) {
            mkdir($dirName, 0777, true);
        }
        $cmd = "mv " . $dbConfigPath . $file . "  " . $dirName;
        shell_exec($cmd);

        DB::disconnect("nn_pub_{$userId}");
        $this->info($file . '文件内容为执行完成');
    }
}

  

  创新如下,将标识参数拎出函数具体落到实处,

protected function executeSqlByFile($file, $dbConfigPath, $date)
{
    if ($this->getConnectionConfig($file) && $this->file_get_contents($dbConfigPath . $file)) {
        $this->excuteSql($file, $dbConfigPath, $date)
    }
}

  

  • 相隔指令与精通

函数要么做怎么着,要么回答什么,但二者不可兼得。函数应该修改某目标的景色恐怕重返该目的的连锁音信,两样都干就不难导致混乱。

从读者的角度想想,set是指是不是曾经安装过吧?仍旧设置成功吗?

if (set("username", "unclebob")) ...

  

莫不上述代码名称可以更为 setAndCheckExists ,
但仍然没有解决实质性地难题,最好是将指令和精晓分隔开来,代码如下,

if (attributeExists("username")) {
  setAttribute("username", "unclebob")
}

  

  • 选用更加替代重返错误码

  错误处理代码能从主路径中分离出来,阅读的时候,可以直面主路径内容。

Promise.all([
  InventoryService.read({job_id: this.jobId}),
  this.getPlacementType()
]).then((result) => {
  let [inventoryInfo] = result
  if (res.$code !== 0) {
    this.$alert.error(res.$msg)
    this.$loading(false)
  } else {
    let ret = this.getReserveInfo(data)
    if (ret.reservable) {
      this.orderInitFromInventory(inventoryInfo.$data, this.defaultOrderInfo)
    } else {
      this.$alert.error('该库存不能下单,可能原因:库存未计算完成!')
      this.$loading(false)
    }
  }
})
Promise.all([
  InventoryService.read({job_id: this.jobId}),
  this.getPlacementType()
]).then((result) => {
  try {
    let [inventoryInfo] = result
    this.checkResponseCode(inventoryInfo)
    this.isInventoryCanBeOrdered(inventoryInfo.$data)
    this.orderInitFromInventory(inventoryInfo.$data, this.orderInfo)
  } catch (err) {
    this.$alert.error(err.message)
    this.$loading(false)
  }
})

isInventoryCanBeOrdered (data) {
  let ret = this.getReserveInfo(data)
  if (!ret.reservable) {
    throw Error('该库存不能下单,可能原因:库存未计算完成!')
  }
}

checkResponseCode (res) {
  if (res.$code !== 0) {
    throw Error(res.$msg)
  }
},

  

  • 别重复自己。

  重复可能是软件中全体邪恶的来源于。许多准绳与执行都是为控制与消除重复而创立。

created () {
  this.$setServiceLoading('数据初始化中...')
  let tplPromise = this.getCreativeTplList({})
  let p1 = new Promise((resolve, reject) => {
    publisherService.getAll({
      op_status: 'ENABLE'
    }).then(res => {
      if (res.$code !== 0) {
        reject()
      } else {
        this.publisherOptions = res.$data
        resolve()
      }
    })
  })
  let p2 = new Promise((resolve, reject) => {
    publisherService.selectAllRules().then(res => {
      if (res.$code !== 0) {
        reject()
      } else {
        this.protectionOptions = res.$data
        resolve()
      }
    })
  })
  let p3 = new Promise((resolve, reject) => {
    realizeService.selectAllRules().then(res => {
      if (res.$code !== 0) {
        reject()
      } else {
        this.realizeOptions = res.$data
        resolve()
      }
    })
  })
  Promise.all([p1, p2, p3, tplPromise]).then(() => {
    if (this.$route.query.id) {
      this.isEditMode = true
      placementService.read({placement_id: this.$route.query.id}).then((res) => {
        if (res.$code !== 0) {
          this.$alert.error(res.$msg, 3000)
        } else {
          Object.assign(this.formData, res.$data)
          Object.keys(this.formData).forEach(key => {
            if (typeof this.formData[key] === 'number') {
              this.formData[key] += ''
            }
          })
          this.$nextTick(() => {
            res.$data.creative_tpl_info.forEach((tpl) => {
              this.formData.tpls[this.formData.placement_type][tpl.creative_tpl_type].checked.push(tpl.creative_tpl_id)
            })
            this.updateCreativeIds()
          })
        }
      }, () => {
        this.$router.replace({path: '/placement/list'})
      })
    }
  }, () => {
    this.$alert.error('初始化媒体信息失败...')
    this.$router.replace({path: '/placement/list'})
  })
}

  

  消除重复代码,

created () {
  if (!this.$route.query || !this.$route.query.id) return
  this.$setServiceLoading('数据初始化中...')
  Promise.all([
    publisherService.getAll({ op_status: 'ENABLE' }),
    publisherService.selectAllRules({}),
    realizeService.selectAllRules({}),
    this.getCreativeTplList({})
  ]).then((resData) => {
    if (!this.checkResCode(resData)) return
    let [publisherOptions, protectionOptions, realizeOptions] = resData
    this.publisherOptions = publisherOptions
    this.protectionOptions = protectionOptions
    this.realizeOptions = realizeOptions
    this.isEditMode = true
    placementService.read({placement_id: this.$route.query.id}).then((res) => {
      if (!this.checkResCode([res])) return
      Object.assign(this.formData, res.$data)
      Object.keys(this.formData).forEach(key => {
        if (typeof this.formData[key] === 'number') {
          this.formData[key] += ''
        }
      })
      this.$nextTick(() => {
        res.$data.creative_tpl_info.forEach((tpl) => {
          this.formData.tpls[this.formData.placement_type][tpl.creative_tpl_type].checked.push(tpl.creative_tpl_id)
        })
        this.updateCreativeIds()
      })
    })
  })
}

function checkResCode (resData) {
  for (let i = 0, len = resData.length; i < len; i++) {
    let res = resData[i]
    if (res.$code !== 0) {
      this.$alert.error(`初始化媒体信息失败,${res.$msg}`, 3000)
      this.$router.replace({path: '/placement/list'})
      return false
    }
  }
  return true
}

  

  • 别回去null,也别传递null

  javascript中,须要重返值的,别回去null/undefined,也别传递null/undefined,除非分外需要。
一旦再次来到值存在null,就代表每一个调用的地点都要看清、处理null,否则就便于并发不足预料的景色。
如下方代码所示,

public void registerItem(Item item) {
  if (item !== null) {
    ItemRegistry registry = peristentStore.getItemRegistry();
    if (registry != null) {
      Item existing = registry.getItem(item.getID());
      if (existing.getBillingPeriod().hasRetailOwner()) {
        existing.register(item);
      }
    }
  }
}

  

从而,在投机可以操纵的函数中(不可控因素如:用户输入),别回去null,也别传递null,别让空判断搞乱了代码逻辑。


  • 归咎案例

据悉《clean code》来看,上边那么些函数有以下多少个方面需求核对,

  1. 函数太大
  2. 代码重复
  3. 函数命名不具有描述性
  4. 局地注释地点放置不适宜
  5. 一点行字符数量太多

 

//注册账号
public function create($data)
{
    //检查是否可以注册
    $check = [
        'tdd'        => $data['tdd'],
    ];
    foreach ($check as $field => $value) {
        $exist = $this->userService->check($field, $value);
        if($exist) {
            throw new RichException(Error::INFO_UNIQUE, [[Error::INFO_UNIQUE,['QQ']]]);
        }
    }
    $userId = $data['user_id'];
    //检查主账号是否存在
    $exist = $this->userService->check('user_id', $userId);
    if(!$exist) {
        throw new RichException(Error::INFO_NOT_FIND_ERROR);
    }
    //姓名账号内唯一
    $exist = (new UserModel($userId))->where('operate_name', '=', $data['operate_name'])->where('user_id', '=', $userId)->where('deleted', '=', 0)->first();
    if($exist) {
        throw new RichException(Error::INFO_UNIQUE, [[Error::INFO_UNIQUE,['姓名']]]);
    }

    $time = date('Y-m-d H:i:s');
    //基本信息
    $exist = (new UserModel($userId))->where('tdd', '=', $data['tdd'])->where('user_id', '=', $userId)->where('deleted', '=', 1)->first();
    if($exist) {
        (new UserModel($userId))->where('tdd', '=', $data['tdd'])->update([
            'operate_name'  => $data['operate_name'],
            'remarks'   => isset($data['remarks']) ? $data['remarks'] : '',
            'tdd'        => $data['tdd'],
            'time'  => $time,
            'operate_status' => UserModel::DEFAULT_STATUS,
            'user_id'   => $userId,
            'deleted'   => 0,
        ]);
    } else {
        (new UserModel($userId))->insert([
            'operate_name'  => $data['operate_name'],
            'remarks'   => isset($data['remarks']) ? $data['remarks'] : '',
            'tdd'        => $data['tdd'],
            'time'  => $time,
            'operate_status' => UserModel::DEFAULT_STATUS,
            'user_id'   => $userId,
            'deleted'   => 0,
        ]);
    }
    //删除账号同样可以创建
    $exist = (new UserQQModel())->where('tdd','=', $data['tdd'])->where('deleted', '=', 1)->first();
    if($exist) {
        (new UserQQModel())->where('tdd', '=', $data['tdd'])->update([
            'tdd' => $data['tdd'],
            'user_id' => $userId,
            'user_type' => UserInfoModel::USER_TYPE_OPT,
            'time' => $time,
            'deleted'   => 0,
        ]);
        //删除原角色信息
        (new OptUserRoleModel($userId))->where('tdd','=', $data['tdd'])->delete();
    } else {
        (new UserQQModel())->insert([
            'tdd' => $data['tdd'],
            'user_id' => $userId,
            'user_type' => UserInfoModel::USER_TYPE_OPT,
            'time' => $time,
            'deleted'   => 0,
        ]);
    }
    //角色信息
    if(isset($data['role_ids']) && is_array($data['role_ids'])) {
        $OptRole = array();
        foreach ($data['role_ids'] as $item) {
            if($item) {
                $opt = [
                    'user_id'   => $userId,
                    'tdd'    => $data['tdd'],
                    'role_id'   => $item,
                    'time'  => $time,
                ];
                $OptRole[] = $opt;
            }
        }
        //更新角色数量信息---暂时不做维护
        if($OptRole) {
            (new OptUserRoleModel($userId))->insert($OptRole);
        }
    }
    //记录日志
    $operateType = BusinessLogConst::CREATE;
    $operateObjectName = $data['operate_name'];
    $operateId = $data['tdd'];
    $operateAction = ['operate_name' => $data['operate_name'], 'remarks'   => isset($data['remarks']) ? $data['remarks'] : '', 'user_id'   => $userId, 'role_ids' => isset($data['role_ids']) ? json_encode($data['role_ids']) : ''];
    $res = $this->writeLog($operateType, $operateObjectName, $operateId, $operateAction);

    return ['user_id' => $userId, 'tdd' => $data['tdd']];
}

  

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图