JAVA-web 项目研发中如何保持团队研发风格的统一
前言
我们在实际项目的开发或者开源项目维护过程中,经常遇到修改或审阅同事代码的情况,每当我们打开别人的代码后,五花八门的格式,不仅增加了我们的阅读负担,也是我们对工作的乐趣丧失。空格 缩进 引号等群魔乱舞,看得人有种身陷泥潭的无力感。
为了解决这个问题,降低阅读和交流的困难,保持代码方格的统一,不管是从代码的管理上还是团队成员的管理上都显得格外重要。
当然作为一个想要依靠无字天书,不想早早被"优化"的人员来说,可能会不适。
本次我们讨论的项目环境是:后端JAVA语言开发,前端vue的项目模式。
预先利其器,必先立规矩!
统一研发工具:
- idea java代码编写
- vscode vue代码编写
前端项目统一约定
插件安装
在vscode的插件市场上,我们需要安装以下插件:
- Prettier - Code formatter
- ESLint
- Project Manager
- Vue (Official)
- vue3-snippets-for-vscode
- Vue Peek with base components
- Dependi 可以较为方便的在package上看到项目依赖版本与最新版本的差距
除了第1,2个必须,其他的都是推荐。
项目安装依赖
she
pnpm add -D prettier eslint eslint-config-prettier eslint-plugin-prettier
- prettier 代码格式化插件
- eslint 纠错插件
- eslint-config-prettier
- eslint-plugin-prettier
必须安装,第三个第四个就是为了更好的解决 eslint 和 prettier 之间 "吵架"的问题。
- ESLint 负责逻辑层面的代码质量检查,如未使用变量、潜在错误等
- Prettier 专注代码格式美化,如缩进、引号、换行等视觉规范
- 两者职责分离,但默认配置可能产生规则冲突,需明确优先级
项目下创建统一约定文件
创建.prettierrc.cjs 文件
我这里创建的是.js 文件,你也可以创建其他的格式文档,json yml toml 等,你喜欢就行, 这里选择 js 是因为可以写注释。
文件内容如下:
js
// @see: https://www.prettier.cn
module.exports = {
// 超过最大值换行
printWidth: 130,
// 缩进字节数
tabWidth: 2,
// 使用制表符而不是空格缩进行
useTabs: true,
// 结尾不用分号(true有,false没有)
semi: true,
// 使用单引号(true单双引号,false双引号)
singleQuote: false,
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
quoteProps: "as-needed",
// 在对象,数组括号与文字之间加空格 "{ foo: bar }"
bracketSpacing: true,
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
trailingComma: "all",
// 在JSX中使用单引号而不是双引号
jsxSingleQuote: false,
// (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号 ,always:不省略括号
arrowParens: "avoid",
// 如果文件顶部已经有一个 doclock,这个选项将新建一行注释,并打上@format标记。
insertPragma: false,
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行
proseWrap: "preserve",
// 在html中空格是否是敏感的 "css" - 遵守CSS显示属性的默认值, "strict" - 空格被认为是敏感的 ,"ignore" - 空格被认为是不敏感的
htmlWhitespaceSensitivity: "strict",
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: "auto",
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeStart: 0,
rangeEnd: Infinity,
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
jsxBracketSameLine: false, // JSX 标签的右括号是否与前一行的末尾对齐,默认为 false
usePrettierrc: true, // 是否使用项目根目录下的 .prettierrc 文件,默认为 true
overrides: [ // 针对特定文件或文件类型的格式化配置
{
files: "*.json", // 匹配的文件或文件类型
options: {
tabWidth: 4 // 针对该文件类型的配置选项
}
},
{
files: "*.md",
options: {
printWidth: 100
}
}
]
};
创建对应的 .prettierignore 文件
js
#忽略 dist 目录
dist
#忽略 public 目录
public
#忽略 node_modules 目录
node_modules
#忽略 public 目录
.vscode
#忽略 src 目录下所有 .test.js 文件
src/**/*.test.js
src/**/*.test.ts
##忽略 types 目录下所有文件
types/**/*
src/types/**/*
#忽略所有 .log 文件
*.log
#忽略所有子目录中的 build 目录
**/build/
#忽略所有 .md 文件
**/*.md
创建 .eslintrc.cjs 文件
你也可以 npm init @eslint/config 自动生成该文件
js
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
extends: [
"eslint:recommended",
"plugin:prettier/recommended", // 启用 Prettier 推荐配置并关闭冲突规则
"plugin:import/recommended", // 放在最后一项,把可能和 Prettier 冲突的规则关掉
],
plugins: ["prettier"],
rules: {
"prettier/prettier": "error", // 将 Prettier 格式问题视为 ESLint 错误
// 这里只放"逻辑相关"的规则
"no-unused-vars": "warn",
"no-constant-condition": "warn",
},
};
vscode setting.json 的配置
其实就是配置一下:
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
为了方便可以在 项目分目录 .vscode 文件夹下创建 setting.json
json
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
至此 工具的配置和使用就完成了,但是为了保障提交代码时按照严格模式 走,还需要集成以下 git的 提交工具 。
Git代码提交流程约定
lint-staged + husky
- lint-staged 是一个用于在 Git 暂存文件上运行任务(如代码格式化和检查)的工具,旨在防止不符合规范的代码提交到代码库
- husky
shell
pnpm add -D husky lint-staged
在package 中添加命令
js
"scripts": {
"dev": "vite",
"build": "vite build",
"prepare": "husky install",
"pre": "cd .. && husky install font/.husky",
"preview": "vite preview"
},
prepare 为 husky 官方 指令, 执行后会在 项目根目录下生成 .husky 文件夹, pre 用于 .git 和 package.json 不在一个目录时。
配置lint-staged , 这里我们在项目下重建 .lintstagedrc.json, 当然你也可以写在package.json 中
js
{
"*.{js,jsx,ts,tsx}": ["eslint", "prettier --write"],
"*.{less,scss}": "prettier --write",
"*.{js,css,json,md}": ["prettier --write"]
}
配置完成后,当你尝试提交代码时,Husky会触发pre-commit钩子,lint-staged会对暂存的文件执行lint检查(如ESLint),commitlint会校验提交信息是否符合规范。如果检查或校验失败,提交将被阻止,并显示错误信息。
后端项目的配置
插件安装
- checkStyle
Checkstyle 是一款开源的代码静态检查工具,主要用于 强制遵循代码规范(如命名规则、代码格式、注释要求等),支持 Java 等语言。它通过预定义或自定义的规则集,自动检测代码中不符合规范的问题(如类名未用 PascalCase、方法缺少 Javadoc 注释、多余的空行等),帮助团队统一编码风格、提升代码可读性和可维护性
官网介绍:https://checkstyle.sourceforge.io/
IDEA 配置
- 在项目根目录下创建 checkstyle.xml 代码格式化文件
xml
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!-- Copyright (C) 2019 check Holding Ltd. All Rights Reserved. -->
<module name="Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="error"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Headers Begin -->
<!-- See https://checkstyle.sourceforge.io/config_header.html -->
<!-- <module name="RegexpHeader">-->
<!-- <property name="header" value="Copyright \(C\) \d{4} check Holding Ltd. All Rights Reserved.$"/>-->
<!-- </module>-->
<!-- Headers End -->
<!-- Miscellaneous Begin -->
<!-- See https://checkstyle.sourceforge.io/config_misc.html -->
<module name="UniqueProperties"/>
<!-- Miscellaneous End -->
<!-- Size Violations Begin -->
<!-- See https://checkstyle.sourceforge.io/config_sizes.html -->
<module name="FileLength"/>
<module name="LineLength">
<property name="max" value="120"/>
<!-- Ignore all imports -->
<property name="ignorePattern" value="^import "/>
</module>
<!-- Size Violations End -->
<!-- Whitespace Begin -->
<!-- See https://checkstyle.sourceforge.io/config_whitespace.html -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<!-- Whitespace End -->
<module name="TreeWalker">
<!-- Annotations Begin -->
<!-- See https://checkstyle.sourceforge.io/config_annotation.html -->
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationMostCases"/>
<property name="tokens"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationVariables"/>
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="AnnotationLocation"/>
<module name="MissingOverride"/>
<!-- Make the @SuppressWarnings annotations available to Checkstyle -->
<module name="SuppressWarningsHolder"/>
<!-- Annotations End -->
<!-- Block Checks Begin -->
<!-- See https://checkstyle.sourceforge.io/config_blocks.html -->
<module name="AvoidNestedBlocks"/>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
</module>
<module name="LeftCurly"/>
<module name="RightCurly">
<property name="id" value="RightCurlySame"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE,
LITERAL_DO"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlyAlone"/>
<property name="option" value="alone"/>
<property name="tokens"
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
INSTANCE_INIT"/>
</module>
<module name="NeedBraces"/>
<!-- Block Checks End -->
<!-- Class Design Begin -->
<!-- See https://checkstyle.sourceforge.io/config_design.html -->
<module name="InterfaceIsType"/>
<!-- <module name="MutableException"/>-->
<module name="OneTopLevelClass"/>
<module name="ThrowsCount"/>
<!-- Class Design End -->
<!-- Coding Begin -->
<!-- See https://checkstyle.sourceforge.io/config_coding.html -->
<!-- <module name="AvoidDoubleBraceInitialization"/>-->
<module name="AvoidNoArgumentSuperConstructorCall"/>
<module name="CovariantEquals"/>
<module name="DefaultComesLast"/>
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="FallThrough"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format"
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message"
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
</module>
<module name="InnerAssignment"/>
<module name="MissingSwitchDefault"/>
<module name="MultipleVariableDeclarations"/>
<module name="NestedForDepth">
<property name="max" value="3"/>
</module>
<module name="NestedIfDepth">
<property name="max" value="3"/>
</module>
<module name="NestedTryDepth">
<property name="max" value="2"/>
</module>
<module name="NoClone"/>
<module name="NoFinalizer"/>
<module name="OneStatementPerLine"/>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="PackageDeclaration"/>
<!-- <module name="ParameterAssignment"/>-->
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<module name="StringLiteralEquality"/>
<!-- <module name="UnnecessaryParentheses"/>-->
<module name="UnnecessarySemicolonAfterTypeMemberDeclaration"/>
<module name="UnnecessarySemicolonInTryWithResources"/>
<module name="VariableDeclarationUsageDistance"/>
<!-- Coding End -->
<!-- Imports Begin -->
<!-- See https://checkstyle.sourceforge.io/config_imports.html -->
<module name="AvoidStarImport"/>
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
<module name="ImportOrder">
<property name="groups" value="java,javax,org,com"/>
<property name="ordered" value="true"/>
<property name="separated" value="true"/>
<property name="option" value="TOP"/>
</module>
<module name="RedundantImport"/>
<module name="UnusedImports"/>
<!-- Imports End -->
<!-- Javadoc Comments Begin -->
<!-- See https://checkstyle.sourceforge.io/config_javadoc.html -->
<module name="JavadocParagraph">
<property name="allowNewlineParagraph" value="false"/>
</module>
<module name="JavadocStyle">
<property name="checkFirstSentence" value="false"/>
</module>
<module name="JavadocTagContinuationIndentation"/>
<module name="JavadocType">
<property name="scope" value="public"/>
</module>
<module name="MissingJavadocMethod"/>
<module name="MissingJavadocPackage"/>
<module name="MissingJavadocType"/>
<module name="NonEmptyAtclauseDescription"/>
<module name="SingleLineJavadoc">
<property name="ignoreInlineTags" value="false"/>
</module>
<module name="SummaryJavadoc">
<property name="period" value=""/>
</module>
<!-- Javadoc Comments End -->
<!-- Metrics Begin -->
<!-- See https://checkstyle.sourceforge.io/config_metrics.html -->
<module name="BooleanExpressionComplexity">
<property name="max" value="7"/>
</module>
<!-- <module name="ClassDataAbstractionCoupling">-->
<!-- <property name="excludeClassesRegexps" value=".*Response$,.*Request$"/>-->
<!-- </module>-->
<!-- <module name="ClassFanOutComplexity">-->
<!-- <property name="excludeClassesRegexps" value=".*Response$,.*Request$"/>-->
<!-- </module>-->
<module name="CyclomaticComplexity"/>
<!-- <module name="JavaNCSS"/>-->
<!-- Metrics End -->
<!-- Miscellaneous Begin -->
<!-- See https://checkstyle.sourceforge.io/config_misc.html -->
<module name="ArrayTypeStyle"/>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="CommentsIndentation"/>
<module name="Indentation">
<property name="basicOffset" value="4"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="4"/>
<property name="throwsIndent" value="8"/>
<property name="lineWrappingIndentation" value="8"/>
<property name="arrayInitIndent" value="4"/>
</module>
<module name="OuterTypeFilename"/>
<module name="UpperEll"/>
<!-- Miscellaneous End -->
<!-- Modifiers Begin -->
<!-- See https://checkstyle.sourceforge.io/config_modifier.html -->
<module name="ModifierOrder"/>
<!-- Modifiers End -->
<!-- Naming Conventions Begin -->
<!-- See https://checkstyle.sourceforge.io/config_naming.html -->
<!-- <module name="AbbreviationAsWordInName">-->
<!-- <property name="ignoreFinal" value="false"/>-->
<!-- <property name="allowedAbbreviationLength" value="1"/>-->
<!-- </module>-->
<module name="CatchParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<!-- <module name="ClassTypeParameterName">-->
<!-- <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>-->
<!-- <message key="name.invalidPattern"-->
<!-- value="Class type name ''{0}'' must match pattern ''{1}''."/>-->
<!-- </module>-->
<module name="ConstantName"/>
<module name="InterfaceTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Interface type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LambdaParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern"
value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypeName">
<message key="name.invalidPattern"
value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<!-- Naming Conventions End -->
<!-- Size Violations Begin -->
<!-- See https://checkstyle.sourceforge.io/config_sizes.html -->
<module name="AnonInnerLength"/>
<!-- <module name="ExecutableStatementCount"/>-->
<module name="MethodCount"/>
<module name="MethodLength">
<property name="max" value="200"/>
</module>
<module name="OuterTypeNumber"/>
<module name="ParameterNumber">
<property name="max" value="9"/>
</module>
<!-- Size Violations End -->
<!-- Whitespace Begin -->
<!-- See https://checkstyle.sourceforge.io/config_whitespace.html -->
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoLineWrap"/>
<module name="NoWhitespaceAfter"/>
<module name="NoWhitespaceBefore">
<property name="tokens" value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, METHOD_REF"/>
<property name="allowLineBreaks" value="true"/>
</module>
<module name="OperatorWrap">
<property name="option" value="NL"/>
<property name="tokens"
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/>
</module>
<module name="ParenPad"/>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapDot"/>
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapComma"/>
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 -->
<property name="id" value="SeparatorWrapEllipsis"/>
<property name="tokens" value="ELLIPSIS"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 -->
<property name="id" value="SeparatorWrapArrayDeclarator"/>
<property name="tokens" value="ARRAY_DECLARATOR"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapMethodRef"/>
<property name="tokens" value="METHOD_REF"/>
<property name="option" value="nl"/>
</module>
<module name="SingleSpaceSeparator"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
<property name="ignoreEnhancedForColon" value="false"/>
<message key="ws.notFollowed"
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
<message key="ws.notPreceded"
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
</module>
<!-- Whitespace End -->
<module name="SuppressionCommentFilter"/>
</module>
<module name="SuppressWarningsFilter"/>
</module>
- IDEA 默认代码风格修改

往下面拉

这里注意,这是为了让每个java 类文件的import 有固定格式,这个格式需要和 上面checkstyle.xml 中的
xml
<module name="ImportOrder">
<property name="groups" value="java,javax,org,com"/>
<property name="ordered" value="true"/>
<property name="separated" value="true"/>
<property name="option" value="TOP"/>
</module>
保持一致,如果对这个没有那么多的要求,可以不用管理, 这个是为了审阅 代码有无三房依赖的快捷办法。
为了好看,图上每个类型之间添加了空行分割。
- 关闭所有的自动操作

都是关闭的。
IDEA Git提交配置
-
添加maven 插件
xml<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> <version>3.1.1</version> <!-- 使用合适的版本 --> <executions> <execution> <goals> <goal>check</goal> </goals> </execution> </executions> <configuration> <configLocation>path/to/checkstyle.xml</configLocation> </configuration> </plugin> -
创建 pre-commit git hook 钩子
Git 仓库的
.git/hooks/pre-commit文件中shell#!/bin/sh # 检查当前是否有未通过 Checkstyle 检查的文件 if ! mvn checkstyle:check; then echo "Checkstyle failed, commit aborted." exit 1 fi如果不喜欢,也可以:
