https://www.zhihu.com/question/20035837
代码规范 ——— 命名,日志, 异常,注释,面向对象 (和公司的 风格一致)
最好能 见名知意. 否则隔个一年半载,就很难知道当时是什么意思
维度—-codeview
- run,正确、 —- bug free
- 安全性/健壮性, 输入检查, 异常处理, 边界检查
- 对输入的处理,1去空格, 2判断格式 3转换格式后再比较
- 代码 执行 上千遍, 数据量上千时的情况。
- 各种边界条件,如何测试, test cases 才是 重中之重
- 讨论多线程,分布式情况,——量增加 10000倍怎么弄,放到1000台机器上如何运行,
- 性能依赖合理性
- 安全性/健壮性, 输入检查, 异常处理, 边界检查
- 性能、 —- 机器执行代码,对代码要求——快。
- 可维护性\拓展性
- 高内聚低耦合 —- 代码导入引用关联代码,对代码要求——松耦合
- 重复代码封装成函数 ,每个 block 内聚性好
- 留下可扩展的空间
- 优雅,易读 —- 人阅读代码,对代码要求——可读。
- self-document
- 控制复杂度
- 不要编写大段代码
- 去掉不必要的 method,参数,变量 (特别是只用一次那种)
- 结构清晰; 分层, 分段, 分块;
- 函数命名 ; 用简单的表达; 把 代码功能 相近的 放在一起
- 最好能 见名知意. 否则隔个一年半载,就很难知道当时是什么意思
- 当别人读你的代码时, 不明白。
- 高内聚低耦合 —- 代码导入引用关联代码,对代码要求——松耦合
2、代码重复:顾名思义就是重复的代码,如果你的代码中有大量的重复代码,你就要考虑是否将重复的代码提取出来,封装成一个公共的方法或者组件。
3、代码覆盖率:测试代码能运行到的代码比率,你的代码经过了单元测试了吗?是不是每个方法都进行了测试,代码覆盖率是多少?这关系到你的代码的功能性和稳定性。
具体流程
- 阅读文档,分析需求,提前设计
- 画原型图或草图(方便自己理解整体架构)
- 写大纲或伪代码(如果项目比较大还要细分模块)
- 实现细节
提前设计
在接到一个需求时,千万不要看完需求就马上写代码,以免造成返工或误解需求的现象。在这个阶段一定要多问,看完需求后,在脑里过一下,把可能会涉及的情况都要问清楚。
一名好的程序员不仅仅是一名程序员,还要懂需求、业务。
在把情况都了解清楚后,如果项目规模不是很大,就可以开始写大纲了。
例如这样:
class Car {
run(){},
stop(){},
}
然后再开始实现细节。
如果项目规模比较大,可以通过思维导图或其他工具写一个项目的原型(当然这种活一般都会由项目经理或产品来负责),再细分到不同的程序模块来一一实现。
“磨刀不误砍柴功”,千万不要为了求快而直接写代码。
测试
无论是单元测试、自测或者是其它测试,最重要的目的都是为了找出尽可能多的BUG,保证产品的质量。
好的东西都是迭代改出来的,比如好的产品,好的架构,代码也不例外,写的好的代码都是经历了作者不停地 review 和修改。
测试的过程本身就是一个自我 code review 的过程,在这个过程中,可以发现一些设计上的问题(比如代码设计的不可测试),代码编写方面的问题(比如一些边界条件的处理不当)等,做到及时发现及时修正,不需要等到测试阶段甚至上线之后再发现再修改
example
分层规范
合理的代码分层,能控制各层的复杂度,以分层的思路去设计,也能提高代码的复用性。对于分层,我认为熟悉的就是好的,能满足工作中的大部分情况就好,这里不谈六边形架构、清晰架构、DODAF等概念,自己驾驭不了,还不能拿出来吹。我推荐DDD最基础的4层分层架构,如下:
用户界面/接口层
⇩
应用层
⇩
领域层
⇩
基础设施层
这里举个我实际项目中用到的例子:
-- bootstrap
-- BeanConfig
-- application
-- pv
-- ChannelPvApplicationService
-- sns
-- domain
-- abtest
-- AbtestService
-- address
-- coupon
-- entity
-- Coupon
-- CouponStatus
-- CategoryCouponTemplate
-- category
-- user
-- UserRepository
-- service
-- OneIdService
-- UserService
-- item
-- ItemRepostory
-- live
-- LiveStatus
-- infrastructure
-- concurrent
-- ThreadPoolExecutorFactory
-- MonitorableCallerRunsPolicy
-- dal
-- IGraphDal
-- TuringDal
-- DefaultUserRepository
-- dao
-- MybatisItemDao
-- util
-- DateUtil
-- MoneyUtil
-- UriUtil
-- monitor
-- Event
-- Timing
-- TimingAspect
-- TimingEvent
-- Monitors
-- view
-- atomicwidget
-- BannerWidget
-- CrazySubsidyWidget
-- FeedItemsWidget
-- NavigateBarWidget
-- LiveWidget
-- page
-- HomeScreenPage
-- CategoryFeedsPage
-- SearchCardPage
-- widget
-- Widget
-- DispatchableWidget
-- Debuggable
-- AbstractWidget
-- AbstractDispatchableWidget
-- WidgetDispatcher
-- WidgetResult
-- WidgetContextIncompatibleException
上述项目结构中,因为是导购项目,view相当于用户界面层,application是应用层,domain是领域层,infrastructure是基础设施层。
再对包的划分说明一下:
- 领域对象、值对象、DTO、Service等定义都放在子域的包下,不要有大而全的entity、service、impl等包(这里的子域是一个内聚的逻辑概念,对应的是领域设计里的子域,如上例中的item在我们的导购里就是商品这个子域)
- 常量定义尽量跟着相关的类走,作为类的静态字段,不要有大而全的Constant类(Switch相关的除外,但也要按职责尽量拆分开关类)
代码规范
代码规范就推荐阿里经济体开发规约,很全面,也是阿里同学的基本要求。代码规范就推荐「阿里经济体开发规约」,很全面,也是阿里同学的基本要求,开源版本:阿里巴巴java开发手册 https://github.com/alibaba/p3c
结合自己的经验,重点说几点:
命名
- 命名不用泛称(反例:processData)
- 尽量用完整的单词描述清楚作用和意图,不要怕字多
- 对象后缀领域对象不带后缀DTO:RPC接口提供的对象以作为VO:跟前端交互的对象PO:跟数据库直接交互的对象
日志
- 所有后台都要有操作日志、数据变更日志
- 日志要配置异步写盘
- 线上仅保留WARN和ERROR级别日志
- 所有日志都要有traceId
- 异常日志要有堆栈、入参、能说清楚是什么错误的信息(可以出统一组件)
- 打印日志时,禁止直接用JSON工具将对象转换成String
异常
- 怎么抛:尽量使用非受检异常,提高代码可读性
- 怎么处理:统一异常用切面处理,或依赖SpringMvc的ControllerAdvice统一处理
- 异常catch范围尽量小,分清稳定代码和非稳定代码
- 禁止直接吞掉异常
- 时刻警惕NPE,多用Optional处理
注释
- 注释只为了说明为什么这么做,不用来说明是在做什么
面向对象
- 遵循原则:SRP/OCP/LSP/ISP/DIP
- 尽量只暴露行为,不暴露数据
- 慎用继承,优先使用组合方式
其它规范
- 方法行数保持在一屏之内(30行以内)
- 代码提交commit message一定要讲清楚做了啥控制每次提交的代码量(一个功能一提交)
- 参数尽量用不可变对象(不对入参做修改,保持明确的入参和出参)尽量不用隐式入参(ThreadLocal)
- 数据结构无随机读取时,用LinkedList替代ArrayList
-
拼写,语意明确
```diff
if 0 != os.system(unsquashfs_cmd):
- msg = “Can’t unsquashf *.squash to a directory; \
- msg = “Can’t unsquashfs *.squash to a directory; \
raise UnsquashfsException(msg)the cmd is: % s " % (unsquashfs_cmd)
try:
check_call(sif_dump_cmd, shell=True)
check_call(unsquashfs_cmd, shell=True)
except CalledProcessError as e:
- msg = “Can’t make a Dockerfile based on your trained model; \
- the cmd is : %s” % e.cmd
ENTRYPOINT [“python”, “/opt/letrain-run-script/deploy_service/deploy_service.py”,\ “—scenario={{ tarined_model_scenario }}”,\
- “—deploy_path={{ tarined_model_location }}”, “—port=27200”,\
- “—deploy_path={{ tarined_model_location }}”, “—port=80”,\ “—model_name={{ tarined_model_name }}”]
<a name="iT9iL"></a>
#### 把 代码功能 相近的 放在一起, 易读。
```diff
def make_dockerfile(self, request, model_id):
SCENARIO_CHOICES = ("classification", "objectdetection",
"segmentation")
DOCKERFILE = 'Dockerfile'
SLURM_LETRAIN_IMAGE = 'letrain.image'
model = TrainedModel.objects.get(id=model_id)
model_location = self.get_model_location(model.id)
model_name = json.loads(model.parameters).get('model_name')
model_scenario = SCENARIO_CHOICES[model.type]
model_path_end_slash = model_location + '/model/'
- docker_file_path = self.fopr.path_join(model_location, 'Dockerfile')
context = {
'model_path_end_slash': model_path_end_slash,
'tarined_model_scenario': model_scenario,
'tarined_model_location': model_location,
'tarined_model_name': model_name,
}
from django.conf import settings
if settings.LICO.ARCH == 'kube':
from lico.core.container.docker.models import DockerImage
image = DockerImage.objects.get(name='letrain')
context['base_image'] = image.image_path
elif settings.LICO.ARCH == 'host':
from lico.core.container.singularity.models import SingularityImage
image = SingularityImage.objects.get(name='letrain', username='')
new_image_path = self.fopr.path_join(
model_location, SLURM_LETRAIN_IMAGE)
self.fopr.copyfile(image.image_path, new_image_path)
+ docker_file_path = self.fopr.path_join(model_location, 'Dockerfile')
with self.fopr.open_file(docker_file_path, 'w') as f:
new_content = render_to_string(DOCKERFILE, context=context)
f.file_handle.write(new_content)
return model_location
去掉不必要的 函数,变量,参数
def make_dockerfile(self, request, model_id):
SCENARIO_CHOICES = ("classification", "objectdetection",
"segmentation")
DOCKERFILE = 'Dockerfile'
- SLURM_LETRAIN_IMAGE = 'letrain.image'
- LETRAIN_DATA_PATH = "data"
- LETRAIN_DATA_SQUASH = "data.squash"
- SIF_DUMP_CMD = "singularity sif dump 3 %s > %s"
- UNSQUASHFS_CMD = 'unsquashfs -dest %s %s'
model = TrainedModel.objects.get(id=model_id)
model_location = self.get_model_location(model.id)
- model_path_end_slash = os.path.join(model_location, 'model/')
- model_scenario = SCENARIO_CHOICES[model.type]
- model_name = json.loads(model.parameters).get('model_name')
-
context = {
- 'model_path_end_slash': model_path_end_slash,
- 'tarined_model_scenario': model_scenario,
+ 'model_copy_dest': self.fopr.path_join(model_location, 'model/'),
+ 'tarined_model_scenario': SCENARIO_CHOICES[model.type],
'tarined_model_location': model_location,
- 'tarined_model_name': model_name,
+ 'tarined_model_name': json.loads(model.parameters).get('model_name'),
}
from lico.core.container.singularity.models import SingularityImage
image = self.get_letrain_image(SingularityImage)
image_path = self.fopr.path_join(
- model_location, SLURM_LETRAIN_IMAGE)
+ model_location, 'letrain.image')
self.fopr.copyfile(image.image_path, image_path)
- data_squash = self.fopr.path_join(
- model_location, LETRAIN_DATA_SQUASH)
- data_path = self.fopr.path_join(
- model_location, LETRAIN_DATA_PATH)
- NEW_SIF_DUMP_CMD = SIF_DUMP_CMD % (image_path, data_squash)
- NEW_UNSQUASHFS_CMD = UNSQUASHFS_CMD % (data_path, data_squash)
- context['letrain_data_path'] = LETRAIN_DATA_PATH
+ letrain_data_path = "data"
+ data_path = self.fopr.path_join(model_location, letrain_data_path)
+ data_squash = self.fopr.path_join(model_location, "data.squash")
+ sif_dump_cmd = "singularity sif dump 3 %s > %s" \
+ % (image_path, data_squash)
+ unsquashfs_cmd = 'unsquashfs -dest %s %s' \
+ % (data_path, data_squash)
os.system(sif_dump_cmd)
os.system(unsquashfs_cmd)
+ context['letrain_data_path'] = letrain_data_path
self.fopr.remove(image_path)
copy 文件,目录 时 检查 是否已经存在:
image_name='letrain-gpu')
elif settings.LICO.ARCH == 'host':
from django.conf import settings
- letrain_data_path = "data"
- self.fopr.copytree(
- self.fopr.path_join(
- settings.CONTAINER.AI_CONTAINER_ROOT, 'letrain-data'),
- self.fopr.path_join(model_location, letrain_data_path))
+ letrain_data_path = "letrain-image-output-data-dir"
+ data_absolute_path = self.fopr.path_join(
+ model_location, letrain_data_path)
+ if not self.fopr.path_exists(data_absolute_path):
+ self.fopr.copytree(
+ self.fopr.path_join(
+ settings.CONTAINER.AI_CONTAINER_ROOT, 'letrain-data'),
+ data_absolute_path)
context['letrain_data_path'] = letrain_data_path
docker_file_path = self.fopr.path_join(model_location, 'Dockerfile')