目录

代码评审

类、对象和数据结构

编写整洁的函数

异常处理

单元测试


代码评审

  1. 参与代码评审过程中的两个角色分别是什么?

作者 评审人

  1. 应当和谁商定参加代码评审的人选?

项目经理或产品负责人,其他开发人员,测试与运维人员

  1. 在代码评审前如何做能够节省评审人的时间和精力?

自我评审,编写文档,代码规范检查,单元测试

  1. 在进行代码评审时必须注意哪些方面?

代码质量,代码安全,代码正确性,代码可维护性,代码性能,代码复用

  1. 三种反馈类型分别是什么?

正面反馈,鼓励,肯定

建设性反馈   提出意见和建议

不合适的反馈,个人主管看法或错误的陈述

类、对象和数据结构

  1. 如何在C#中组织类?

在C#中,可以使用命名空间(namespace)来组织类。命名空间是一种将相关类、接口、枚举等元素组织在一起的方式,以便更好地管理和使用它们。

  1. 一个类应该有多少种职责?

一个类应该只有一个职责,这就是所谓的单一职责原则。这个原则意味着,一个类应该只有一个引起它变化的原因,也就是说,一个类应该只有一个责任。如果一个类承担了过多的职责,那么它将会变得不稳定、可维护性差、可读性差、难以重用,甚至可能会引发一系列的问题。因此,一个类应该只负责一件事情,并且把这件事情做好。

  1. 如何添加代码注释以便生成文档?
  • 在C#和.NET中,可以通过添加XML注释生成文档。XML注释是放在代码上的特殊的注释格式,包含有标准的XML标签和注释内容,这些注释可以用指定的工具生成文档。常见的XML标签包括:
  • <summary>:对成员的描述;
  • <param>:对方法的参数进行描述;
  • <returns>:对方法返回值进行描述;
  • <exception>:对方法可能抛出的异常进行描述;
  • <seealso>:引用一个其他成员。
  1. 请解释内聚的含义。

内聚(Cohesion),科学名词,是一个模块内部各成分之间相关联程度的度量。按内聚程度从低到高排列,通常分为偶然内聚、逻辑内聚、时间内聚、过程内聚、通信内聚、顺序内聚、功能内聚。其中,偶然内聚和逻辑内聚的模块联系松散,后面几种内聚相差不多,功能内聚一个功能、独立性强、内部结构紧密,是最理想的内聚。

  1. 请解释耦合的含义。

耦合(Coupling)是指一个类与另一个类之间的依赖关系。其中一个类的实现需要依赖于另一个类的实现。这种耦合会导致系统的复杂性增加,因为一个类的修改可能会影响其他类的实现。因此,在软件设计中,应该尽量减少类耦合,使各个类之间保持相对独立的状态。

  1. 内聚应该高还是应该低?

内聚应该尽可能高,因为高内聚意味着模块或组件内部的元素之间的联系非常紧密,彼此之间交流和合作的频率很高,从而提供更好的性能和可靠性。低内聚意味着模块或组件内部的元素之间的联系很弱,彼此之间交流和合作的频率很低,使得系统难以设计和维护,需要更多的时间和资源。因此,高内聚对于软件系统来说是非常重要的。

  1. 耦合应该紧还是应该低?

在软件设计中,耦合是一个非常重要的概念,它表示软件系统各部分之间的相互依赖程度。一般来说,低耦合是较理想的状态,因为它可以降低软件系统的复杂性,提高可维护性和可扩展性。然而,完全消除耦合是不可能的,因为即使是使用面向对象编程的现代软件开发方法,对象之间仍然需要相互通信和协作。

  1. 哪些机制有助于为变化而设计?

以下是一些机制,有助于为变化而设计:

接口抽象:定义良好的接口,从而限制模块的耦合,允许实现细节进行更改,而无需修改其使用者。

分层架构:分离模块和功能,实现松耦合,能够让不同层次之间的变化保持相对独立。

模块化设计:将系统分解成模块、组件等相对独立的部分,能够降低变化对系统的影响范围。

设计模式:采用设计模式可以提高系统的可维护性和可扩展性,从而降低系统变化的代价。

代码复用:利用现有且可靠的代码库,减少重复编写代码、降低错误率,从而更加容易进行系统的变化。

测试驱动开发:TDD(Test Driven Development)模式能够使开发者通过测试保证系统在进行变化时不出现大的问题。

  1. 何谓DI?

DI(Dependency Injection,依赖注入)是一种设计模式,它用于解决系统中不同组件或模块之间依赖关系过于紧密的问题。DI的基本思想是将组件之间的依赖关系从实现中解耦出来,使得依赖关系能够在运行时动态地设置,并且能够很方便地进行修改和替换。

在DI中,对象并不会通过自己创建它所需要的对象,而是通过一个框架或容器来提供所需要的对象。这个框架会根据配置文件或其他方式,把不同对象之间的依赖关系注入到对象内部,从而实现了不同对象之间的解耦和灵活性。

通过采用DI模式,系统的组件之间可以非常灵活、可重用,便于进行单元测试和模块化设计。因此,DI模式已成为现代软件开发中非常受欢迎的一种设计模式。

  1. 何谓IoC?

IoC是Inversion of Control(控制反转)的简称,是一种设计模式,用于较松耦合地连接和组织程序组件。通俗地说,IoC就是通过外部容器控制程序运行的流程和行为。

在传统应用程序中,程序的控制权通常由开发人员直接编写的代码来决定,程序的各个组件之间相互协作和依赖也较为紧密,这种方式的缺点是改动和维护成本较高,可扩展性和可测试性也不足。而IoC则是将程序的控制权交给容器来决定,程序组件之间的依赖关系被抽象化,通过接口和依赖注入实现松耦合,便于后续扩展和修改。

实现IoC的容器在.NET领域中主要有两种,一个是Microsoft提供的框架,如ASP.NET MVC框架中的依赖注入容器,另一个是第三方提供的框架,如AutoFac、Ninject等开源框架。这些容器都提供了一些基础的功能,如将一个实例注入到另一个实例、单实例和多实例等,可以帮助我们更好地实现控制反转和依赖注入。

  1. 请说出使用不可变对象的一个好处。

使用不可变对象的一个好处是可以提高代码的安全性和可靠性。因为不可变对象一旦创建,其状态就不能被修改,从而避免了并发修改带来的问题,比如数据竞争、死锁等。此外,不可变对象还能够避免副本共享和值传递的问题,因为对不可变对象的任何修改都是返回一个新的对象,不会影响原始对象,从而避免了意外修改原始对象带来的风险。同时,使用不可变对象还能够提高代码的可读性和可维护性,因为不可变对象更容易理解、调试和测试,更易于扩展和重用。因此,在设计和实现软件系统时,应该尽可能地使用不可变对象。

  1. 对象应该隐藏哪些内容而又展示哪些内容?

内部变量和属性,只允许通过对象的公开接口来访问和修改对象的状态。

实现细节,包括对内部数据进行处理的过程和方法,避免对外暴露对象的实现细节,以防止 对对象的破坏和侵入。

对象的实现类和类的具体实现,保持对象的抽象性,使其与具体实现分离。

同时,对象应该展示:

对象的属性和状态,包括可以读取和修改的数据、方法、属性、事件等。

对象的行为和功能,即对象提供的操作和服务,包括方法、属性、事件等。

  1. 结构体应该隐藏哪些内容而又展示哪些内容?

使用 private 访问修饰符来限制属性和字段的访问范围,只允许在结构体内部访问;使用 public 或 internal 访问修饰符来表示对外公开的方法和属性。

使用属性和方法来访问结构体的内部数据,从而控制对数据的访问。

隐藏结构体中的字段,而是通过属性来访问。

编写整洁的函数

  1. 没有参数的方法的特定英文术语是什么?

Niladic

  1. 包含一个参数的方法的特定英文术语是什么?

Monadic

  1. 包含两个参数的方法的特定英文术语是什么?

Dyadic

  1. 包含三个参数的方法的特定英文术语是什么?

Triadic

  1. 包含多于三个参数的方法的特定英文术语是什么?

polyadic

  1. 上述方法中哪两种方法应当避免,为什么?

Triadic和polyadic,因为参数过多,不利于测试和可读性,可维护性

  1. 请使用通俗语言解释什么是函数式编程。

不会修改数据或状态

  1. 函数式编程的优点有哪些?

代码简洁,开发快速:函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。

接近自然语言,易于理解:函数式编程的自由度很高,可以写出很接近自然语言的代码。

更方便的代码管理:函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。 因此,每一个函数都可以被看做独立单元,很有利于进行单元测试和除错,以及模块化组合。

易于并发编程:函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。

代码的热升级:函数式编程的代码比较简洁,修改起来比较方便,可以实现代码的热升级。

  1. 说出函数式编程的一个缺点。

函数式编程的主要缺点是可读性较差。因为函数式编程使用了一系列的函数和表达式,使得代码比较抽象,需要花费一定的时间去理解代码的逻辑。此外,在函数式编程中,通常需要使用一些高级的编程技巧,如高阶函数和闭包等,对于初学者来说可能难以理解和学习。

  1. WET的代码是什么样的代码,为什么应当避免编写这种代码?

缺乏复用性:WET代码难以被其他代码复用,造成了不必要的重复劳动和浪费。

可读性和可维护性差:重复的代码增加了代码量,使代码难以理解,且当需要修改某个功能时需要分别修改所有涉及到的代码,容易出现错误。

不易测试和维护:如果一段代码重复多次,则对它的单元测试需要分别进行多次测试,从而增加测试和维护的成本。

为了避免编写WET代码,可以采用以下方法:

抽象化通用功能:将重复的代码抽象成函数、类、模块等,以方便复用和维护。

使用设计模式:设计模式可以帮助开发者快速构建可扩展、可维护的代码结构,避免重复编写相似的代码。

引入依赖管理:使用依赖管理工具(如Maven或Gradle)可以轻松地引入和更新第三方库,减少不必要的重复开发。

  1. DRY的代码是什么样的代码,为什么应当编写这种代码?

可读性高:重复的代码会降低代码的可读性和维护性。如果代码中存在重复的代码,那么在修改这些代码时,必须分别修改每个地方,这可能会导致遗漏或错误。而DRY代码只需要在一个地方进行修改,从而提高代码的可读性和维护性。

易于维护:DRY代码只需要在一个地方进行修改,因此可以更容易地维护和更新。

扩展性强:DRY代码可以通过函数、类等方法重用和扩展,从而实现更加灵活的代码架构。

  1. 如何将WET的代码转换为DRY的代码?

查找重复的代码:首先需要查找代码中的重复部分,这些重复部分通常是由于代码的某个部分被多次复制和粘贴而导致的。

抽象出重复的代码:将重复的代码抽象出来,封装成函数、类等可重用的组件。这样可以将重复的代码替换为调用这些组件的代码,从而避免重复。

提取共性:寻找代码中的共性,将共性的代码抽象出来,从而避免重复。例如,如果多个地方都有相似的数据处理逻辑,可以将这个处理逻辑提取出来,作为独立的函数或方法。

使用继承和多态:如果代码中有多个类或对象具有相似的属性和方法,可以使用继承和多态来实现代码复用。这样可以将相同的代码放在父类或接口中,而不同的代码放在不同的子类中,从而实现代码复用。

  1. 为什么方法应当尽量短小?

可读性高:方法越短小,就越容易被理解和阅读。当方法的代码行数较少时,可以更轻松地理解方法的功能和实现方式,从而提高代码的可读性。

易于维护:短小的方法更容易维护和更新。当方法的代码行数较少时,可以更容易地找到和修改代码中的错误,从而提高代码的可靠性和稳定性。

扩展性强:短小的方法更具有可扩展性。当方法的代码行数较少时,可以更容易地添加新功能、修复错误以及优化性能,从而更好地满足未来的需求。

避免过度封装:方法的短小并不意味着需要过度封装。过度封装可能会使方法难以理解和修改,从而降低代码的可读性和维护性。

  1. 如何无须使用try/catch块实现(数据)验证工作。

使用断言(Assertions):在许多编程语言中,断言是一种用于验证代码假设的机制。断言通常使用条件语句来检查数据是否满足某些条件,如果条件不满足,则会引发异常。断言通常用于调试和测试目的,而不是用于生产环境中的数据验证。

使用函数参数验证:在函数中验证数据是一种常见的做法。通过编写具有良好文档注释和输入参数验证的函数,可以在不使用try/catch块的情况下完成数据验证。这种方法需要对输入数据进行预期和处理,并且通常需要对返回数据进行转换。

使用错误代码:对于那些返回错误码的函数或操作,可以在调用处使用错误码来判断操作是否成功,而不必捕获异常。这种方法通常用于系统调用或库函数中,因为它们需要返回一个错误码来表示操作是否成功。

使用约定:在某些情况下,可以使用约定来验证数据。例如,如果数据必须为整数,则可以在函数中添加一个参数验证来确保输入参数为整数。这种方法需要定义一组规则,并且确保在应用程序中的所有部分都遵守这些规则。

异常处理

  1. 什么是检查型异常?

检查型异常是指在编程中使用的异常,由编译器执行检查,以确保异常被捕获或处理。这类异常通常被称为“必检异常”,因为它们必须被开发者处理,否则程序将无法通过编译。

与之相对的是非检查型异常,也被称为“免检异常”,因为编译器不会强制要求开发者处理这些异常。这类异常通常是程序运行时出现的错误,例如数组越界、空指针引用等。

  1. 什么是非检查型异常?

非检查型异常是指在编程中使用的异常,由编译器执行检查,以确保异常被捕获或处理。这类异常通常被称为“免检异常”,因为编译器不会强制要求开发者处理这些异常。常见的非检查型异常包括ArrayIndexOutOfBoundsException、NullPointerException等。

与之相对的是检查型异常,也被称为“必检异常”,因为它们必须被开发者处理,否则程序将无法通过编译。这类异常通常是由程序运行时出现的错误引起的,例如文件未找到、IO异常等。

  1. 什么是算术溢出异常?

算术溢出异常(arithmetic overflow)是指在计算机程序中,在进行算术运算时,产生的结果超出了目标数据类型的取值范围。这通常会导致程序出现异常或错误。

  1. 什么是NullReferenceException?

NullReferenceException是.NET Framework中的一个异常,也被称为“空引用异常”,它是在尝试访问空对象时抛出的异常。

  1. 如何验证null参数以从整体上改善代码?

使用条件语句:使用if或 ternary运算符来检查参数是否为null,并在检查后执行相应的操作。

使用三目运算符:使用三目运算符(也称为条件运算符)可以更简洁地检查null参数并执行相应的操作。

使用方法链:使用方法链可以更方便地验证null参数,并执行相应的操作。

使用函数式编程:使用函数式编程可以更灵活地验证null参数,并执行相应的操作。

使用异常处理:在处理null参数时,可以使用异常处理机制来捕获和处理异常情况,以避免程序崩溃或产生其他错误。

  1. BRE全称是什么?

Business rule Exception

  1. 业务规则异常是良好的实践还是不当的实践?为什么?

业务规则异常是一种不当的实践。它们用于在不明确异常的特定信息或代码上下文的情况下轻松抛出自定义异常,也就是不明确的情况下快速返回值,这种情况将会使程序运行出现错误。

因此,业务规则异常不是一种良好的实践,应该尽量避免使用。

  1. 业务规则异常的替代品是什么?它是良好的实践还是不当的实践?为什么?

通过控制流来判断请求是否被接受,是良好的实践,。因为通过控制流来判断,可读性强,性能好

  1. 如何提供有意义的异常消息?

使用有意义的异常类名:为异常提供一个描述性的类名,以便开发者可以快速识别异常类型。

详细说明异常原因:在异常消息中包含有关异常原因的信息,以便开发者可以确定错误的根本原因。

提供有用的上下文信息:在异常消息中包含有关应用程序的上下文信息,例如文件路径、方法参数和堆栈跟踪等信息,可以帮助开发者更好地理解异常发生的背景。

使用一致的格式:使用一致的格式和语言来编写异常消息,可以帮助开发者更容易地理解异常消息的含义。

记录异常信息:在应用程序中记录异常信息,以便开发者可以查看和分析这些信息,并确定错误的根本原因。

  1. 编写自定义异常需要满足哪些条件?

创建一个新的异常类,该类应继承自内置的Exception类或其子类。

在异常类中添加一个有意义的构造函数,该函数可以接收一些参数,例如异常消息或其他上下文信息。

添加必要的方法和属性,以便在异常发生时提供有关异常的信息。

在代码中使用自定义异常类,以便在特定情况下引发异常。

单元测试

  1. 什么是良好的单元测试?

可重复性:测试应该可以反复运行,每次运行的结果都应该一致。

自动化:测试应该可以自动运行,而不需要人工干预。

独立性:每个测试用例应该相互独立,不应该依赖于其他测试用例的结果。

全面性:测试应该覆盖所有可能的情况,包括边界情况和异常情况。

可读性:测试代码应该易于阅读和理解,以便其他开发人员可以快速了解测试的目的和结果。

可维护性:测试代码应该易于维护,以便随着代码的变化而更新测试用例。

可靠性:测试应该能够准确地检测出代码中的错误和缺陷。

  1. 良好的单元测试不该有什么行为?

不应该依赖于其他测试用例的结果

不应该依赖于测试环境中其他组件或资源

不应该包含对测试框架或库的硬编码

不应该包含不可控的随即行为

  1. TDD是什么意思?

TDD是测试驱动开发(Test-Driven Development)的英文简称,是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,然后测试代码会驱动产品代码的编写。这样,在编写产品代码的同时,也会不断优化和改进测试代码,从而提高了代码的质量和可维护性。TDD的核心思想是“先写测试,再写代码”,强调了测试代码的重要性,以及测试代码和产品代码的同步开发。因此,TDD可以帮助开发者更早地发现和解决问题,提高开发的效率和代码的质量。TDD通常适用于敏捷开发流程和DevOps开发模式,但它也可以应用于其他开发方法和过程。

  1. BDD是什么意思?

BDD是行为驱动开发(Behaviour-Driven Development)的英文简称,是一种敏捷开发方法。BDD基于TDD(测试驱动开发),并扩展了TDD的实践,使用业务语言来编写自动化测试,以便更好地描述用户行为,从而使开发人员更好地理解需求和设计系统。BDD的核心理念是“专注于用户行为,而非代码细节”,通过使用自然语言描述来定义系统的行为和功能,提高测试用例的可读性和可维护性。BDD通常与TDD结合使用,但它的重点在于使用业务语言来描述用户行为和需求,而不是测试代码的实现细节。BDD适用于各种开发流程和方法,包括敏捷开发和DevOps开发模式。

  1. 什么是单元测试?

单元测试是软件测试的一种基本方法,用于验证软件的单个单元(如函数、方法、类等)是否符合预期行为。单元测试是软件开发过程中的一个关键环节,也是实现自动化测试和持续集成的基础。单元测试通常由开发人员编写和执行,以确保软件的各个部分在隔离的环境下正常工作,并且符合设计要求。单元测试是最低级别的测试活动,它通常在集成和系统测试之前进行。通过单元测试可以发现和纠正软件中的一些基础问题,从而提高软件的质量和可靠性。

  1. 什么是替身对象?

替身对象(Mock Object)是单元测试中常用的一种测试对象,用于模拟依赖关系和外部资源,以便在测试环境中隔离和替换这些依赖和资源。替身对象通常用于模拟数据库、文件系统、网络服务、外部库等复杂或不可控的组件和资源,以便在测试环境中控制输入和输出,并确保测试的独立性和可重复性。在Python中,有许多库可以用于创建和操作替身对象,如Mock、Faker、PyMock等。使用替身对象可以帮助开发人员更轻松地编写和执行单元测试,从而提高测试的覆盖率和效率。

  1. 什么是伪造对象?

伪造对象是一种测试技术,用于模拟子类型构造函数中超类型构造函数的行为。伪造对象是在子类型构造函数内部调用超类型构造函数的机制,使得子类型实例可以继承超类型实例的属性和方法,同时还可以接受在子类型构造函数中定义的参数。伪造对象通常用于测试目的,使得在测试环境中可以控制和模拟复杂依赖关系的行为。伪造对象的一个常见应用是在面向对象编程中,模拟基类或抽象类的行为,以便更好地测试派生类或实现类的行为。

  1. 请说出一些单元测试框架的名称。

MSTest NUnit xUnit JUnit

  1. 请说出一些替身框架的名称。

Moq

  1. 请说出一个BDD框架的名称。

SpecFlow

  1. 哪些内容应当从代码文件中删除?

所有注释和不必要的空格。

调试代码和临时修复代码。

不使用的代码和函数。

所有测试代码和不必要的测试数据。

所有不必要的依赖库和框架。

所有不必要的日志和异常处理代码。

所有不必要的循环和重复代码。

所有不必要的变量和常量。

所有不必要的函数和类。

所有不必要的配置文件和环境变量。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐