《Clean Code》 代码简洁之道

第2天 栈和寄存器

笔者介绍

原稿作者: 罗Bert C. 马丁, Object
Mentor公司首席执行官,面向对象设计、方式、UML、敏捷方理学和终端编程领域的盛名顾问,是《敏捷软件开发:原则、方式、与实践》的撰稿人。
翻译作者:韩磊,网络产品与运营专家,技术书籍著译者。译著有《梦断代码》和《C#编程风格》等。(竟然不是程序员~~~)

多文本编程

作者在暗中和许多C语言的爱好者和初学者沟通的长河中发现,大家已经可以运用C语言做出来很美妙的顺序了。但是这么些赏心悦目的顺序中的一有的甚至唯有一个源文件。所以,作者决定要介绍一下哪些选择多少个源文件进行编程。不得不说,多文本编程有不行多的优势。比如在尊敬上卓殊有利,同时也给五人搭档提供了便民。当然,小编自我最快乐多文件编程的某些就是因为它看起来舒服。接下来,大家就共同来学习怎么着进展多文本编程。

进去DEV-C++集成开发条件之后,在顶部菜单中找到“文件”→“新建”→“项目”。在弹出的对话框中挑选”空项目”(Empty
Project),注意还亟需选用开发的言语并给品种起名。点击确定后需求对工程文件举办封存。

起名 1

而后请在左边的类型管理中找到刚刚新建的类型,新建的门类默认有一个源代码文件,请将它移除并在类型中添加大家在明天编辑的源文件。

起名 2

接下去我们需要在我们的体系中新建一个源文件,并把它定名为 kia_style.h
实际上它是一个头文件。那样大家就足以把我们事先编写的除了最大旨框架的局地都扔到
kia_style.h 那几个头文件中。于是 kia_style.h 就改成了:

#include<stdio.h>
#include<conio.h>
#include<string.h>
#include<stdlib.h>
#include<windows.h>

#ifndef KIA_STYLE_H
#define KIA_STYLE_H

unsigned short kia_fc=7;
unsigned short kia_bc=0;

void kia_refreshcolor() {
    HANDLE hCon=GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hCon,(kia_fc%16)|(kia_bc%16*16));
}

void kia_color(unsigned short c) {
    kia_fc=c;
    kia_refreshcolor();
}

void kia_out(char *str) {
    unsigned short kia_sfc=kia_fc;
    if(strcmp(str,"none")==0)
        kia_color(7);
    printf("%s",str);
    kia_color(kia_sfc);
}

void kia_outln(char *str) {
    unsigned short kia_sfc=kia_fc;
    if(strcmp(str,"none")==0)
        kia_color(7);
    printf("%s\n",str);
    kia_color(kia_sfc);
}

void kia_limout(unsigned char lim,char *str,char style) {
    unsigned char i=0,j=0,len=strlen(str);
    if(len<=lim) {
        kia_out(str);
        for(i=0; i<(lim-len); i++)
            kia_out(" ");
        if(style!=0)
            kia_outln("");
    }
    if(len>lim) {
        if(style==0) {
            for(i=0; i<(lim-3); i++)
                printf("%c",str[i]);
            kia_out("...");
        } else {
            while((j*lim+i)<len) {
                for(i=0; i<lim; i++) {
                    printf("%c",str[j*lim+i]);
                    if((j*lim+i)>=len)
                        break;
                }
                if(style==abs(style))
                    printf("\n");
                if((j*lim+i)<len)
                    for(i=0; i<abs(style); i++)
                        printf(" ");
                j++;
            }
            if(style!=abs(style))
                printf("\n");
        }
    }
}

void kia_tag(char *str) {
    strlwr(str);
    if(strcmp(str,"tip")==0) {
        kia_color(10);
        kia_limout(10,"tip",0);
    } else if(strcmp(str,"error")==0) {
        kia_color(12);
        kia_limout(10,"error",0);
    } else if(strcmp(str,"warning")==0) {
        kia_color(13);
        kia_limout(10,"warning",0);
    } else {
        kia_color(11);
        kia_limout(10,str,0);
        kia_color(14);
    }
}

#endif

这样一来,主文件就会变得那一个的概括清爽。根据小编的习惯,主文件被命名为
main.c

#include "kia_style.h"

int kia_end() {
    kia_tag("tip");
    kia_limout(69,"Press any key to return.",10);
    getch();
    kia_fc=7;
    kia_bc=0;
    kia_refreshcolor();
    return 0;
}

int main(int argc, char *argv[]) {
    SetConsoleTitle("kiasm virtual machine");
    kia_tag("tip");
    kia_limout(69,"test-00000-test-11111-test-22222-test-33333-test-44444-test-55555-test-66666-test",10);
    kia_tag("error");
    kia_limout(69,"test-00000-test-11111-test-22222-test-33333-test",10);
    kia_tag("warning");
    kia_limout(69,"test-00000-test-11111-test-22222-test-33333-test",10);
    kia_tag("test");
    kia_limout(69,"test-00000-test-11111-test-22222-test-33333-test",10);
    kia_tag("testtesttest");
    kia_limout(69,"none",10);
    return kia_end();
}

那边有两件事须要引起特其他注意。首先在 kia_style.h 头文件中

#ifndef KIA_STYLE_H
#define KIA_STYLE_H

//省略程序块

#endif

这里#ifndef#define#endif都是预处理命令,它们的功用是防止头文件中的函数和变量被重新注解。

协助,在主文件中只须求包含一个 kia_style.h
头文件,因为主函数没有一直调用其余的头文件,在kia_style.h
头文件中调用的其余头文件应该在kia_style.h
头文件中富含,而不是在主文件中涵盖。此外值得注意的是,由于 kia_style.h
头文件是我们机关开发的头文件,所以它要被双引号包裹,而不是被尖括号包裹。

后天能够点击编译并运行试试看,是否得到了和前天相同的结果。

内容大致

本书后几章重点讲了java相关的类、系统、和出现的筹划介绍,较不难,与简单之道不是特地融合,故而简单,想要详细精通的提出去看更优质的详实讲解。
本书主要站在代码的可读性上研讨。可读性? 顾名思义,代码读起来简单易懂,
令人心境快意,大加褒扬。
起名,在N年从此,自己或者旁人仍旧可以稍加阅读就能领略其中的情致。什么是清新代码?看看程序员鼻祖们怎么说的,

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

  2. 清新代码从不隐藏设计者的企图。—— Grady Booch,
    《面向对象分析与统筹》作者

不可能想着,那代码我能看懂就好了,
即使当下能看懂,那多少个月甚至一年过后呢。更无法说为了反映团结编程“高大上”,故意用有些不为人知的语法,如

const LIMIT = 10
const LIMIT = Number(++[[]][+[]]+[+[]])
  1. 尽可能少的依靠关系,模块提供尽量少的API。—— 戴夫 Thoms, OTI创办人,
    Eclipse战略教父
  2. 代码应该通过其字面说明含义,命名和情节保持一致。 —— 迈克尔Feathers, 《修改代码的艺术》小编
  3. 压缩重复代码,甚至尚未重新代码。—— Ron Jeffries,
    《C#极端编程探险》作者
  4. 让编程语言像是专门为竭泽而渔不行标题而留存。 —— 沃德 Counningham,
    Wiki发明者

​​

无类型语言的建立

固然说在那多少个曾经丰裕强壮的编程语言中无类型语言已经是过去时了。不过即使我们一初始就建立一种有品种语言的话,如同会给大家创设编程语言的经过带来众多附加的担当。所以,暂时来说作者仍然要两手空空一种无类型的言语,在此起彼伏的章节中自己会告诉您,有项目语言是怎么样贯彻的。

所谓无类型语言并不是说一种数据类型都尚未的语言,它唯有一种数据类型。那种数据类型可以按照我们的喜好来支配。小编最喜爱的系列是无符号的字符型,即
unsigned char
。拔取那序列型作为无类型语言的唯一的数据类型的原由就是它很容易就能变成任何的档次。为大家继承创设有项目标编程语言提供了巨大的有利。假如每两次都要写
unsigned char 来定义那种数据类型未免太过于辛勤了。所以大家利用
typedefunsigned char 起一个别名供我们选择。那里小编起的名字是
kia_t

typedef unsigned char kia_t;

预言后事怎样,请听下回分解。关怀自身,第一时间获得立异动态。

有含义的命名

  • 名副其实

好的变量、函数或类的称号应当已经答复了所有的大难点,即使急需注释补充,就不算名副其实。
工具函数内部的临时变量可以稍微能接到。

// 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

注释

什么样也不如放置杰出的笺注来的有效性。
怎么也不会比乌烟瘴气的评释更有本事搞乱一个模块。
何以也不会比陈旧、提供错误音讯的声明更有破坏性。
若编程语言丰硕有表达力,或者大家善用用这一个语言来表述意图,就不那么需求注释——也平昔不需求。

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

申明会撒谎。由于程序员无法坚称维护注释,其存在的日子越久,离其所讲述的代码越远,甚至最终可能完全错误。不确切的表明比向来不注释坏的多,净瞎说,真实只在一处地点存在:代码。

  • 诠释的熨帖用法是弥补我们在用代码表明意图时受到的失利。

    // 禁用、解冻
    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 () {}

  

好注释

  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地图