代码团队化管理之代码格式化

团队代码格式化方案,大家一起写出漂亮的代码吧~
Views: 909
2 0
Read Time:3 Minute, 25 Second

在一个大型项目中,我们往往都需要通过团队协作去达成我们的目标。由于个人风格不同,当众多开发者一起开发时,难免会遇到代码风格上的问题。代码风格的不同会导致我们Review时不能很好的捕捉具体的变动,尤其是Git提交后,格式的改动会让评审时浪费额外的精力。为了保证不同开发者的代码可读性与美观性,我们需要借助格式化工具来将每个人的代码进行格式化,且保持格式化之后的风格统一。格式化的方式有很多,比较推荐的是使用Visual Studio Code自带的格式化功能或者借助clang-format程序完成。

其中Visual Studio Code自带的格式化可能会随着版本不同而变更,如果使用Clang-format,在保证.clang-format文件保持不变的情况下,可以保证代码格式化后的效果一致性。所以最好的办法还是将clang-format固化下来。今天这篇博客的内容,就是教大家如何去做clang-format的配置,并实现git提交时自动格式化的功能。需要注意的是,使用clang-format前需要将clang-format的可执行文件路径设置到环境变量内。

如何安装clang-format程序我这里就不再多讲,可以自行去官网下载,也可以借助Visual Studio Code下载插件,在下载完并配置好环境变量后,我们在Windows Cmd下输入:clang-format –version,如果配置正确,我们应该能看到这样的输出:

在配置好之后,我们需要做的就是确定我们的格式化标准参考文件,这份文件通常以.clang-format命名,如何生成自己想要的.clang-format文件呢,这里我简单简绍两种方法:一是通过官方样例修改成自己喜欢的样子,边修改边查看效果,直到满意位置。二是通过已有的代码库,在大家都认可的风格下去自动生成,clang-format文件。

第一种:使用官方模板进行修改

使用官方模板进行修改时需要一边修改一边格式化后看效果,为了这个过程方便一些,我们还是借助Visual Studio Code,安装好插件clang-format或者C/C++插件,设置好clang-format可执行文件的路径以及代码格式化的参照标准,如下所示:

C_CPP:Clang_format_path项用于设置clang-format可执行文件的路径

C_CPP:Clang_format_style项用于设置格式化的标志,设置file即表明将以.clang-format格式化标准文件中的规则项来格式化。

在做完上述设置后,我们还需要设置保存时自动格式化,设置如下:

在设置完成之后,我们使用clang-format -style=Google -dump-config > googleexample命令生成官方的参考模板,在这里,可以-style命令用于设定基准的代码风格,可选的基准风格包括LLVM、Google、Chromium、Mozilla、WebKit等,请注意,由于clang-format程序的不同,生成的模板可能是有差异的。

在生成模板后,我们可以根据需要自行调整参数,在保存后即可观察到格式化之后的风格,我们一边调整一边观察效果,以达到自认为满意的状态。其中关于格式化标准文件中配置项的说明,我们可以参考clang官方:https://clang.llvm.org/docs/ClangFormatStyleOptions.html的Configurable Format Style Options指导说明。

当然了,这样调整的过程可能比较漫长也比较辛苦的。但好处时高度定制化,并且你可以熟悉其中每一项配置变化产生的不同效果。

第二种:根据代码库生成.clang-format

假如你拥有一份代码,你很喜欢这份代码的编码风格,有没有办法可以快捷地生成.clang-format并应用到自己的项目,答案是肯定的。

这种方式需要借助其他工具,那就是unformat,这是一个开源项目,这个项目利用python可以快速生成clang-format文件。其项目的Git地址如下:https://github.com/johnmcfarlane/unformat。我们将这个项目clone到本地(Linux环境内)。

假设你clone到本地路径:/home/franzkafka95/Desktop/unformat-master,而你需要为其生成.clang-format文件的代码位于/home/franzkafka95/Desktop/codebase这个目录,,使用如下命令生成.clang-format文件:

python3 /home/franzkafka95/Desktop/unformat-master/unformat-master –root  /home/franzkafka95/Desktop/codebase /home/franzkafka95/Desktop/codebase/**/*.h   /home/franzkafka95/Desktop/codebase /**/*.cpp

执行后,就会开始自动更具代码生成.clang-format文件,我们可以随时通过Crtl+C打断这个过程。完成后会在代码路径下生成clang-format文件。我们拿到这份文件,直接放入我们需要格式化的代码工程的顶层目录,保存后观察一下效果,如有些地方不满意还可以做调整。

现在我们已经得到了.clang-format文件,将其作为git仓库内的一部分提交,项目其他成员同步下来后使用Visual Studio Code的自动格式化功能即可保证大家的代码风格。

考虑到项目开发成员并不一定都喜欢使用Visual Studio Code,那有没有办法可以做到在我们Commit时自动格式化呢,答案当然时肯定的啦。其实实现起来其实非常容易,我们可以借助.git/hook/pre-clang脚本完成。pre-commit脚本的内容如下:

#!/bin/bash
#NOTE:Please do not  edit this file
STYLE=$(git config --get hooks.clangformat.style)
if [ -n "${STYLE}" ] ; then
    STYLEARG="-style=${STYLE}"
else
#Note:Use current top directory clang-format
    STYLE=$(git rev-parse --show-toplevel)/.clang-format
    if [ -n "${STYLE}" ] ; then
        STYLEARG="-style=file"
    fi
fi
echo "format config file is $STYLE"
format_file() {
  file="${1}"
  echo "Will format $file automatically..."
  if [ -f $file ]; then
    clang-format -i ${STYLEARG} ${1}
    git add ${1}
  fi
}
case "${1}" in
  --about )
    echo "Runs clang-format on source files"
    ;;
  * )
    for file in `git diff-index --cached --name-only HEAD | grep -iE '\.(cpp|cc|h|hpp)$' ` ; do
      format_file "${file}"
    done
    ;;
esac

还需要说明的是.git/hooks/内容是无法提交到git仓库的,那么我们如何将这一份pre-commit文件同步到所有开发者的工程内呢,在这里,我们需要借助第二个脚本,假设命名为DevelopInit.sh,并在我们的git仓库内创建一个名为DevelopmentSetup的目录,将写好的pre-commit文件放在该目录内,编写 DevelopInit.sh 脚本内容如下:

# 获取脚本路径
cd "$(dirname "$BASH_SOURCE")"
script_file=$(pwd)/$(basename "$BASH_SOURCE")
script_path=$(dirname "$script_file")
cd - >/dev/null

#拷贝hooks到对应目录
cp -fv $script_path/DevelopmentSetup/pre-commit  $script_path/.git/hooks/

将DevelopInit.sh脚本放置在与.git同一目录层级下,提交到git代码仓库后团队成员都进行同步并执行 DevelopInit.sh 脚本,执行之后大家的本地仓库都会在提交时自动使用clang-format进行格式化啦。

在这里我也将我自己用的.clang-format文件分享给大家,当然了我的代码风格是C++的,基于LLVM进行修改的,具体如下:

---
Language:        Cpp
# BasedOnStyle:  LLVM
AccessModifierOffset: -4
# 开括号(开圆括号、开尖括号、开方括号)后的对齐:
AlignAfterOpenBracket: Align
# 连续赋值时,对齐所有等号
AlignConsecutiveAssignments: false
# 连续声明时,对齐所有声明的变量名
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
# 水平对齐二元和三元表达式的操作数
AlignOperands: false
# 对齐连续的尾随的注释
AlignTrailingComments: true
# 允许函数声明的所有参数在放在下一行
AllowAllParametersOfDeclarationOnNextLine: false
# 允许短的块放在同一行
AllowShortBlocksOnASingleLine: false
# 允许短的case标签放在同一行
AllowShortCaseLabelsOnASingleLine: false
# 允许短的函数放在同一行
AllowShortFunctionsOnASingleLine: Inline
# 允许短的if语句保持在同一行
AllowShortIfStatementsOnASingleLine: false
# 允许短的循环保持在同一行
AllowShortLoopsOnASingleLine: false
# 总是在定义返回类型后换行(deprecated)
AlwaysBreakAfterDefinitionReturnType: None
# 总是在返回类型后换行:
AlwaysBreakAfterReturnType: None
# 总是在多行string字面量前换行
AlwaysBreakBeforeMultilineStrings: true
# 总是在template声明后换行
AlwaysBreakTemplateDeclarations: false
# false表示函数实参都在同一行
BinPackArguments: true
# false表示所有形参都在同一行
BinPackParameters: false
# 大括号换行,只有当BreakBeforeBraces设置为Custom时才有效
BraceWrapping:
  AfterClass: false
  AfterControlStatement: false
  AfterEnum: false
  AfterExternBlock: false
  AfterFunction: true
  AfterNamespace: false
  AfterObjCDeclaration: false
  AfterStruct: false
  AfterUnion: false
  BeforeCatch: false
  BeforeElse: false
  IndentBraces: false
  SplitEmptyFunction: true
  SplitEmptyNamespace: true
  SplitEmptyRecord: true
BreakAfterJavaFieldAnnotations: true
# 在二元运算符前换行
BreakBeforeBinaryOperators: NonAssignment
# 在大括号前换行
BreakBeforeBraces: Allman

BreakBeforeInheritanceComma: false
# 在三元运算符前换行
BreakBeforeTernaryOperators: false
# 在构造函数的初始化列表的逗号前换行
BreakConstructorInitializers: BeforeComma
BreakConstructorInitializersBeforeComma: true
BreakStringLiterals: true
# 每行字符的限制,0表示没有限制
ColumnLimit: 0
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: true
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 0
ContinuationIndentWidth: 13
Cpp11BracedListStyle: false
DerivePointerAlignment: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: true
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Priority: 2
  Regex: ^"(llvm|llvm-c|clang|clang-c)/
- Priority: 3
  Regex: ^(<|"(gtest|gmock|isl|json)/)
- Priority: 1
  Regex: .*
IncludeIsMainRegex: (Test)?$
IndentCaseLabels: false
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: false
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 15
PenaltyBreakComment: 302
PenaltyBreakFirstLessLess: 98
PenaltyBreakString: 786
PenaltyExcessCharacter: 1141897
PenaltyReturnTypeOnItsOwnLine: 81
PointerAlignment: Left
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: true
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 9
UseTab: Never
...

给大家看看实际效果吧!

格式化前:

格式化后:

不得不说,格式化后还是好看许多。好了,以上就是这篇博客的全部内容了,希望大家会喜欢。如果各位有问题,可以通过评论告诉我,我会一一解答的。

Happy
Happy
100 %
Sad
Sad
0 %
Excited
Excited
0 %
Sleepy
Sleepy
0 %
Angry
Angry
0 %
Surprise
Surprise
0 %
FranzKafka95
FranzKafka95

极客,文学爱好者。如果你也喜欢我,那你大可不必害羞。

文章: 86

一条评论

  1. 请问我想实现以下这种,函数返回值是指针的时候 *挨着void ,变量是指针(或者引用)的时候*挨着变量名。
    void* mMyNcion(int *a, int &b){}

    我看官方支持的是 *号 要么都在左,要么都在右边。

    期待回复,谢谢!

留下评论

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

zh_CNCN