https://www.zhihu.com/question/20035837
知识边界-面试.pngpostman_aouth_2.0_jwt.png
代码规范 ——— 命名,日志, 异常,注释,面向对象 (和公司的 风格一致)

最好能 见名知意. 否则隔个一年半载,就很难知道当时是什么意思

维度—-codeview

  • run,正确、 —- bug free
    • 安全性/健壮性, 输入检查, 异常处理, 边界检查
      • 对输入的处理,1去空格, 2判断格式 3转换格式后再比较
      • 代码 执行 上千遍, 数据量上千时的情况。
      • 各种边界条件,如何测试, test cases 才是 重中之重
      • 讨论多线程,分布式情况,——量增加 10000倍怎么弄,放到1000台机器上如何运行,
    • 性能依赖合理性
  • 性能、 —- 机器执行代码,对代码要求——快。
  • 可维护性\拓展性
    • 高内聚低耦合 —- 代码导入引用关联代码,对代码要求——松耦合
      • 重复代码封装成函数 ,每个 block 内聚性好
    • 留下可扩展的空间
    • 优雅,易读 —- 人阅读代码,对代码要求——可读。
      • self-document
      • 控制复杂度
        • 不要编写大段代码
        • 去掉不必要的 method,参数,变量 (特别是只用一次那种)
      • 结构清晰; 分层, 分段, 分块;
      • 函数命名 ; 用简单的表达; 把 代码功能 相近的 放在一起
        • 最好能 见名知意. 否则隔个一年半载,就很难知道当时是什么意思
        • 当别人读你的代码时, 不明白。

2、代码重复:顾名思义就是重复的代码,如果你的代码中有大量的重复代码,你就要考虑是否将重复的代码提取出来,封装成一个公共的方法或者组件。

3、代码覆盖率:测试代码能运行到的代码比率,你的代码经过了单元测试了吗?是不是每个方法都进行了测试,代码覆盖率是多少?这关系到你的代码的功能性和稳定性。

具体流程

  • 阅读文档,分析需求,提前设计
  • 画原型图或草图(方便自己理解整体架构)
  • 写大纲或伪代码(如果项目比较大还要细分模块)
  • 实现细节

提前设计

在接到一个需求时,千万不要看完需求就马上写代码,以免造成返工或误解需求的现象。在这个阶段一定要多问,看完需求后,在脑里过一下,把可能会涉及的情况都要问清楚。
一名好的程序员不仅仅是一名程序员,还要懂需求、业务。
在把情况都了解清楚后,如果项目规模不是很大,就可以开始写大纲了。
例如这样:

  1. class Car {
  2. run(){},
  3. stop(){},
  4. }

然后再开始实现细节。
如果项目规模比较大,可以通过思维导图或其他工具写一个项目的原型(当然这种活一般都会由项目经理或产品来负责),再细分到不同的程序模块来一一实现。
“磨刀不误砍柴功”,千万不要为了求快而直接写代码。

测试

无论是单元测试、自测或者是其它测试,最重要的目的都是为了找出尽可能多的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; \
      the cmd is: % s " % (unsquashfs_cmd)
    
    raise UnsquashfsException(msg)
    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
  • msg = “Create data directory for dockerfile failed.” logger.exception(msg) ```

    简易

    ```diff

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')

回收 文件,回收目录, 回收内存