CodeReview--提高代码健壮性

前言

没实习之前,不怎么用到版本管理工具。顶多就是用Git把写的一些代码上传到github上,基本上不会涉及到某一个版本怎么怎么地的。年前到唱吧实习的这段时间,倒是接触了不少相关的内容。由于历史原因,后台开发使用的是SVN。在提交代码的时候通过COMMIT信息来实现CodeReview或者真正的代码提交。当时对这块特别好奇,到底是怎么实现的,今天也想着自己做一个简易的版本出来,目的嘛,就当是学习新知识了。

代码提交路线

一般来说,代码提交可能会有这么几个阶段。

  • 随意篇: 自己管理,自己维护,commit就完了,这个阶段基本上不会涉及版本控制。

  • 自测篇: 这个阶段开发成员可能会大于一个人。大家各自开发自己的模块,功能完成后直接提交。最后进行模块间的整合。一般而言,这个阶段开发风险有点大,某个成员离职了,文档不规范等,都会给后续的整合带来困难。一旦出了问题,线上代码回滚就可以了,所以这个阶段版本控制起到了很大的作用。

  • 监督篇: 这个阶段下,一般团队开发人员已经有一定的规模了。这个阶段下的公司更在意代码的质量,而不是简单的追求功能完成了。因此,给团队其他成员进行代码review会进一步提高代码的健壮性,减少出错的几率。类似于小黄鸭测试法,review你的代码的人或许可以轻松的发现你怎么也不会想到的错误。

对我而言,是直接从随意篇蹦到了监督篇了。记得当初写唱吧APP首页的心愿墙接口的时候,代码被review了11次。最后一版不管是从简洁性还是效率上逗比第一版好了太多。这也许就是代码review的好处了吧。可以从优秀的开发工程师身上学到更多实用的经验。

也许有人会觉得,写个代码要先被别人看下,还要浪费这么多时间,我自己测试完事不就行了嘛,何必搞得这么麻烦。其实,一开始我也是这么想的,后来就慢慢的离不开codereview了,老话不是有一句磨刀不误砍柴工吗,被review了的代码在一定程度上会有更高的准确性和执行效率。从长远角度看,后期维护和优化的时间也会少很多。

Code Review简易实现

说了这么多,都是铺垫用的。下面开始一点点的着手进行实现这个简易版本的codereview吧。

Hooks

在SVN仓库的根目录下有一个hooks目录,里面有很多钩子模板。这些钩子的作用就像我们提交一个表单的时候会使用JavaScript做下字段的校验。不同的钩子(文件名去掉tmpl,并且chmod 755权限才可以被正确执行)对应了不同的功能,而对于实现codereview的话,pre-commit钩子就够了。
svn仓库下的hooks

钩子的工作原理就是,在开发人员发起一次svn commit 到中央仓库的时候,会被pre-commit拦截下来。这样便于代码管理员做一些操作。同样代码提交后,同样会被post-commit(如果放开了的话)拦截,做一些通知类的处理逻辑。

SVNLOOK

# svnlook --help
general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]
Subversion repository inspection tool.
Type 'svnlook help <subcommand>' for help on a specific subcommand.
Type 'svnlook --version' to see the program version and FS modules.
Note: any subcommand which takes the '--revision' and '--transaction'
      options will, if invoked without one of those options, act on
      the repository's youngest revision.

Available subcommands:
   author
   cat
   changed
   date
   diff
   dirs-changed
   filesize
   help (?, h)
   history
   info
   lock
   log
   propget (pget, pg)
   proplist (plist, pl)
   tree
   uuid
   youngest

其中可以通过子命令author(获取代码提交者名字), diff(获取代码有改动的地方), log(获取提交代码的注释信息)。用法大同小异。可以参考下面的命令学习不同的使用技巧。

# svnlook author --help
author: usage: svnlook author REPOS_PATH

Print the author.

Valid options:
  -r [--revision] ARG      : specify revision number ARG
  -t [--transaction] ARG   : specify transaction name ARG

通用模板如下

svnlook subcommander -t "$TXN" "$REPOS" | ...

思路

一开始我想的是,模仿公司的做法,将获取到的diff文件内容,通过github的gist服务,直接生成一个方便转发阅读的链接。但是国内现在没法访问这个服务了,很遗憾。然后就打算自己做一个(其实还是太天真了,一点也不优雅)。

因为diff内容可能会很大,因此需要使用post方式,在shell下使用curl发送post本来是一个很随意的事。

URL="http://ip/codereciew/addreview.php"
curl -i -X POST -H "Content-Type: application/json" -d '{"param1": "value1", "param2": "'$VALUE2'"}' $URL

但是不管我怎么测试,都获取不到对应的请求体。很奇怪,没办法,转换下思路。pre-commit把数据持久化到某一个地方,然后通过Python脚本简介将内容POST到对应的添加review的服务上。然后就可以通过getreview被别人看到。

真的是一波三折啊

代码部分

pre-commit*

cat pre-commit
#!/bin/bash

# PRE-COMMIT HOOK
#
# The pre-commit hook is invoked before a Subversion txn is
# committed.  Subversion runs this hook by invoking a program
# (script, executable, binary, etc.) named 'pre-commit' (for which
# this file is a template), with the following ordered arguments:
#
#   [1] REPOS-PATH   (the path to this repository)
#   [2] TXN-NAME     (the name of the txn about to be committed)
#
#   [STDIN] LOCK-TOKENS ** the lock tokens are passed via STDIN.
#
#   If STDIN contains the line "LOCK-TOKENS:\n" (the "\n" denotes a
#   single newline), the lines following it are the lock tokens for
#   this commit.  The end of the list is marked by a line containing
#   only a newline character.
#
#   Each lock token line consists of a URI-escaped path, followed
#   by the separator character '|', followed by the lock token string,
#   followed by a newline.
#
# If the hook program exits with success, the txn is committed; but
# if it exits with failure (non-zero), the txn is aborted, no commit
# takes place, and STDERR is returned to the client.   The hook
# program can use the 'svnlook' utility to help it examine the txn.
#
#   ***  NOTE: THE HOOK PROGRAM MUST NOT MODIFY THE TXN, EXCEPT  ***
#   ***  FOR REVISION PROPERTIES (like svn:log or svn:author).   ***
#
#   This is why we recommend using the read-only 'svnlook' utility.
#   In the future, Subversion may enforce the rule that pre-commit
#   hooks should not modify the versioned data in txns, or else come
#   up with a mechanism to make it safe to do so (by informing the
#   committing client of the changes).  However, right now neither
#   mechanism is implemented, so hook writers just have to be careful.
#
# The default working directory for the invocation is undefined, so
# the program should set one explicitly if it cares.
#
# On a Unix system, the normal procedure is to have 'pre-commit'
# invoke other programs to do the real work, though it may do the
# work itself too.
#
# Note that 'pre-commit' must be executable by the user(s) who will
# invoke it (typically the user httpd runs as), and that user must
# have filesystem-level permission to access the repository.
#
# On a Windows system, you should name the hook program
# 'pre-commit.bat' or 'pre-commit.exe',
# but the basic idea is the same.
#
# The hook program runs in an empty environment, unless the server is
# explicitly configured otherwise.  For example, a common problem is for
# the PATH environment variable to not be set to its usual value, so
# that subprograms fail to launch unless invoked via absolute path.
# If you're having unexpected problems with a hook program, the
# culprit may be unusual (or missing) environment variables.
#
# CAUTION:
# For security reasons, you MUST always properly quote arguments when
# you use them, as those arguments could contain whitespace or other
# problematic characters. Additionally, you should delimit the list
# of options with "--" before passing the arguments, so malicious
# clients cannot bootleg unexpected options to the commands your
# script aims to execute.
# For similar reasons, you should also add a trailing @ to URLs which
# are passed to SVN commands accepting URLs with peg revisions.
#
# Here is an example hook script, for a Unix /bin/sh interpreter.
# For more examples and pre-written hooks, see those in
# /usr/share/subversion/hook-scripts, and in the repository at
# http://svn.apache.org/repos/asf/subversion/trunk/tools/hook-scripts/ and
# http://svn.apache.org/repos/asf/subversion/trunk/contrib/hook-scripts/


REPOS="$1"
TXN="$2"

# Make sure that the log message contains some text.
SVNLOOK=/usr/bin/svnlook
#$SVNLOOK log -t "$TXN" "$REPOS" | \
#   grep "[a-zA-Z0-9]" > /dev/null || exit 1

# Exit on all errors.
set -e

# Check that the author of this commit has the rights to perform
# the commit on the files and directories being modified.
#"$REPOS"/hooks/commit-access-control.pl "$REPOS" $TXN \
#  "$REPOS"/hooks/commit-access-control.cfg

# 获取TXN信息和REPOS信息
#echo $TXN 1>&2
#echo $REPOS 1>&2
# 获取提交代码的作者信息
AUTHOR=$($SVNLOOK author -t "$TXN" "$REPOS")
#echo $AUTHOR >> svn.log 1>&2
# 获取改变的文件,利用gist实现代码的review
DIFF=$($SVNLOOK diff -t "$TXN" "$REPOS")
DIFFPATH="/tmp/forcodereview/reviewcontent"
#echo $DIFF > $DIFFPATH
#echo $DIFF 1>&2
# add some rules for committing that should have commit msg.
LOGMSGLENGTH=$($SVNLOOK log -t "$TXN" "$REPOS" | wc -c)
LOGMSG=$($SVNLOOK log -t "$TXN" "$REPOS")
#echo $LOGMSG 1>&2
# 判断commit信息是否是review的还是maint的
REVIEW="REVIEW"
COMMIT="COMMIT"
REVIEWURL="http://myip/codereview/addreview.php"
choice=`echo $LOGMSG | awk -F: '{print $1}' | tr [a-z] [A-Z]`

##### 曲线救国吧
landmarkversion=$(echo $TXN | awk -F- '{print $1}')
echo $AUTHOR > /tmp/author
echo $landmarkversion > /tmp/txn
echo $LOGMSG > /tmp/logmsg
echo $DIFF > /tmp/diff


if [[ "$choice" == "REVIEW" ]];then
    # 用shell 发送curl指令
    #RESPONSE=$(curl -i -X POST -H "'Content-Type':'application/json'" -d '{"author": "$AUTHOR", "version":"$TXN", "commitmsg": "$LOGMSG", "content":"$DIFF"}' $REVIEWURL)
    #echo $RESPONSE 1>&2
    #$version=$(echo $TXN | awk -F- '{print $1}')
    RESPONSE=$(python /home/svnrepositories/repo/hooks/post.py > /tmp/forcodereview/python-post.result)
    echo "请把下面的链接发送给和你review代码的人:\nhttp://myip/codereview/getreview.php?landmarkversion=$landmarkversion&id=diff" 1>&2
    exit 1
elif [[ "$choice" == "COMMIT" ]];then
    # 可以坐下记录什么的,通知代码管理员或者团队开发.
    # 放行,便于作进一步的提交审核
    echo "恭喜您提交成功啦!" 1>&2
else
    echo "暂不支持的子命令" 1>&2
    exit 1
fi
if [ "$LOGMSGLENGTH" -lt 10 ];then
    echo -e "提交代码需要附带提交的注释,且字符长度别少于10吧,言简意赅就行:)" 1>&2
    exit 1
fi
# All checks passed, so allow the commit.
exit 0

post.py

cat post.py 
#!/usr/bin python
import requests
import sys
import json
# ... 省略获取文本内容的代码。TODO文本方式持久化不好,需要优化下。
payload = {
    "author": author,
    "version": version,
    "commitmsg": commitmsg,
    "content":content
}

url = "http://myip/codereview/addreview.php"
res = requests.post(url=url, data=payload)
print(res.text)

addreview.php

cat addreview.php 
<?php
/**
 * 用于接收来自svn hook 中的pre-commit的shell关于http的请求,解析出对应的文件内容,并添加到review库中。
 * */
$author = $_REQUEST["author"];
$landmarkversion = $_REQUEST["version"];
$TARGETPATH = "/tmp/forcodereview/";
$content = $_REQUEST["content"];
$commitmsg = $_REQUEST["commitmsg"];

$ret = array("code"=>-1, "msg"=>"添加代码review块失败!");
try{
    // 如果目标文件夹不存在则进行创建
    mkdirs($TARGETPATH.$landmarkversion);
    #$reviewname = time();
    $reviewname = "diff";
    $filecontent = "{$author} @ {$landmarkversion} want to commit those changes with `{$commitmsg}`\n{$content}";
    file_put_contents($TARGETPATH.$landmarkversion."/".$reviewname, $filecontent);
    $ret["code"] = 0;
    $ret["msg"] = "添加代码review成功,请移步:\n http://myip/codereview/getreview.php?landmarkversion={$landmarkversion}&id={$reviewname}";
}catch(Exception $e) {
    $ret["msg"] = $e->getMessage();
}
echo json_encode($ret);

function mkdirs($dir, $mode = 0777)
{
    if (is_dir($dir) || @mkdir($dir, $mode)) return TRUE;
    if (!mkdirs(dirname($dir), $mode)) return FALSE;
    return @mkdir($dir, $mode);
}

getreview.php

cat getreview.php 
<?php
/**
 * 用于获取code review结果。通过id代指对应的文件名。
 * landmarkversion:最新代码的下一个版本号
 * id: 防止覆盖的文件review文件名
 * */
$TARGETPATH = "/tmp/forcodereview/";
$landmarkversion = $_REQUEST["landmarkversion"];
$reviewname = $_REQUEST["id"];

$filename = $TARGETPATH.$landmarkversion."/".$reviewname;
$content = file_get_contents($filename);

$data = explode("\n", $content);
$header = $data[0].$data[1];
$body = $data[2];
?>

<div class="container">
    <h3><?php echo $header;?></h3>
    <hr>
    <p><?php print_r($body);?></p>
</div>

具体使用

review模式

在提交信息中使用REVIEW(不区分大小写)作为前缀即可。这样不会真正的提交改动。而是会将改动内容,发送到addreview.php来生成一个链接。用来查看对应的变动内容。
code review模式

commit模式

在提交信息中使用COMMIT(不区分大小写)作为前缀,会绕开review检查,直接提交到版本库中。
code commit模式

总结

  • 磕磕绊绊,服务算是搭建起来了,虽然很不优雅。现在想想,要是一开始gist服务就能用该有多好。

  • nginx部署多个服务且互不影响的实现(这里偷了个懒,我的便签工具是python写的RESTful形式,所以没有后缀名,而PHP刚好有后缀,借此进行了区分。后面如果有多种服务,可能还需要使用不同的转发策略。)

待解决、优化的地方

  • shell下使用curl发送POST请求,这个依旧没解决,对此比较了解的博友们欢迎一起交流。

  • getreview.php实在是太难看了。后面想想怎么来美化一下。方便阅读和浏览。

不过越是有困难,才越能提升自己不是。有需求,就要去实现,过程中难免会遇到各种阻碍,我能做的,就是一个个去面对,去解决。

猜你喜欢

转载自blog.csdn.net/marksinoberg/article/details/79374736