预防式编程——避免空值

news2024/11/25 12:39:28

文章目录

      • 1. 输入验证
      • 2. 使用可选类型(Optional Types)
      • 3. 非空断言
      • 4. 安全调用运算符
      • 5. 提供默认值
      • 6. 设计模式
      • 7. 文档说明
      • 8. 数据结构的选择
      • 9. 逻辑判断
      • 10. 构造函数和初始化
      • 11. 使用工具类
      • 12. 枚举类型
      • 13. 编码规范
      • 14. 测试
      • 15. 重构
      • 16. 教育与培训
    • 案例展示
      • 1. 定义书籍实体类
      • 2. 创建书籍管理服务
      • 3. 处理空值
      • 4. 使用 `Optional` 和默认值
      • 5. 异常处理和日志记录
        • 更新 `BookService`
      • 6. 客户端代码优化
        • 更新 `BookClient`
      • 7. 单元测试
        • 单元测试示例

避免空值(null 或 None 值)是预防式编程的一个重要方面,因为在很多编程语言中,对空值的不当处理可能会导致运行时错误,比如 NullPointerException 在 Java 中,或者在其他语言中类似的错误。以下是一些避免空值的技术:

1. 输入验证

  • 参数检查:在函数或方法开始时检查所有输入参数是否为空。如果检测到空值,则抛出异常或返回错误信息。
    public void process(String input) {
        if (input == null) {
            throw new IllegalArgumentException("Input cannot be null");
        }
        // 正常处理逻辑...
    }
    

2. 使用可选类型(Optional Types)

  • 在一些现代语言中,如 Java 8 及更高版本,引入了 Optional 类型来表示一个值可能存在也可能不存在的情况。这样可以强制调用者显式处理可能的空值。
    public Optional<String> findUserById(Long id) {
        // 模拟查找用户的过程
        User user = userRepository.findById(id);
        return Optional.ofNullable(user.getName());
    }
    
    public void printUserName(Long id) {
        findUserById(id).ifPresent(System.out::println);
    }
    

3. 非空断言

  • 在某些语言中,可以使用编译器特性来声明一个变量或参数不允许为空。例如,在 Kotlin 中,你可以声明一个非空类型,并在赋值时进行检查。
    fun printName(name: String) {  // name 不允许为 null
        println(name)
    }
    

4. 安全调用运算符

  • 一些语言提供了安全调用运算符来避免空指针异常。例如,在 Kotlin 中,可以使用 ?. 运算符。
    val length: Int? = someObject?.name?.length
    

5. 提供默认值

  • 当遇到可能为空的值时,提供一个合理的默认值,这样即使对象为空也不会导致程序崩溃。
    String name = user.getName() != null ? user.getName() : "Unknown";
    

6. 设计模式

  • 工厂模式:可以通过工厂方法来创建对象,工厂负责确保永远不会返回空对象。
  • 单例模式:确保在整个应用中只有一个实例,避免多次初始化可能带来的空值问题。

7. 文档说明

  • 清晰地文档化每个方法的返回值是否可能为空,以及调用者应该如何处理这种情况。

8. 数据结构的选择

  • 使用容器类:在一些场景下,可以使用特定的容器类来代替直接使用基本类型或对象。例如,使用 List 而不是数组,可以避免处理空数组的问题。
    List<String> names = new ArrayList<>();
    // 如果需要一个空列表,可以直接使用空的 List 对象
    

9. 逻辑判断

  • 先判断再使用:在使用某个对象之前,先进行非空检查,确保对象存在后再执行相关操作。
    if (user != null && user.getName() != null) {
        System.out.println(user.getName());
    }
    

10. 构造函数和初始化

  • 确保对象初始化完整:在构造函数中确保所有的必要字段都被正确初始化,避免对象在创建后立即使用时出现空值。
    public class User {
        private String name;
        
        public User(String name) {
            this.name = Objects.requireNonNull(name, "Name cannot be null");
        }
    }
    

11. 使用工具类

  • 使用工具类处理空值:有些语言或框架提供了工具类来帮助处理空值,例如 Apache Commons Lang 的 StringUtilsObjects 类。
    String name = StringUtils.defaultIfEmpty(user.getName(), "Unknown");
    

12. 枚举类型

  • 使用枚举类型替代简单类型:在某些情况下,使用枚举类型可以明确表示某个值的存在与否,从而避免空值问题。
    enum Status {
        ACTIVE, INACTIVE, UNKNOWN
    }
    
    public Status getStatus() {
        // 返回一个枚举值而不是 null
        return Status.UNKNOWN;
    }
    

13. 编码规范

  • 制定编码规范:团队内部可以制定关于如何处理空值的编码规范,确保所有人都遵循相同的规则,减少由于个人习惯不同而导致的问题。

14. 测试

  • 编写针对空值的测试用例:在单元测试中包括针对空值的测试,确保代码在遇到空值时能够正常工作或按预期抛出异常。
    @Test
    public void testProcessWithNullInput() {
        assertThrows(IllegalArgumentException.class, () -> process(null));
    }
    

15. 重构

  • 重构代码:定期检查代码中是否存在对空值的不当处理,并进行必要的重构,以提高代码质量。

16. 教育与培训

  • 加强团队培训:通过培训和分享会增强团队成员对空值问题的认识,提高他们处理空值的能力。

通过综合运用这些策略和技术,可以大大减少空值引发的问题,提高软件系统的健壮性和可靠性。

案例展示

让我们通过一个具体的案例来展示如何综合应用预防式编程技术来避免空值问题。假设我们正在开发一个图书管理系统,该系统需要处理书籍的信息,并能够根据书籍的 ID 查询书籍的详细信息。我们将通过以下几个步骤来展示如何避免空值:

1. 定义书籍实体类

首先定义一个 Book 实体类,包含书籍的基本信息:

public class Book {
    private final Long id;
    private final String title;
    private final String author;
    private final String isbn;

    public Book(Long id, String title, String author, String isbn) {
        this.id = Objects.requireNonNull(id, "ID cannot be null");
        this.title = Objects.requireNonNull(title, "Title cannot be null");
        this.author = Objects.requireNonNull(author, "Author cannot be null");
        this.isbn = Objects.requireNonNull(isbn, "ISBN cannot be null");
    }

    // Getters
    public Long getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }

    public String getIsbn() {
        return isbn;
    }
}

这里我们使用了 Objects.requireNonNull() 方法来确保所有传递给构造函数的参数都不为空。

2. 创建书籍管理服务

接下来定义一个 BookService 类来管理书籍的查询和其他业务逻辑:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class BookService {
    private final Map<Long, Book> books = new ConcurrentHashMap<>();

    public BookService() {
        // 初始化一些书籍数据
        books.put(1L, new Book(1L, "Clean Code", "Robert C. Martin", "978-0132350884"));
        books.put(2L, new Book(2L, "Design Patterns", "Erich Gamma", "978-0201633610"));
    }

    /**
     * 根据书籍 ID 查找书籍。
     *
     * @param bookId 书籍的 ID
     * @return 书籍信息或空(如果找不到)
     */
    public Book findBookById(Long bookId) {
        if (bookId == null) {
            throw new IllegalArgumentException("Book ID cannot be null");
        }
        return books.get(bookId);
    }
}

在这个服务中,我们使用了一个 ConcurrentHashMap 来存储书籍信息。findBookById 方法会根据提供的书籍 ID 查找书籍,并在找不到书籍时返回 null

3. 处理空值

为了更好地处理可能返回的 null 值,我们可以使用 Java 8 引入的 Optional 类型来改进我们的服务:

public Optional<Book> findBookById(Long bookId) {
    if (bookId == null) {
        throw new IllegalArgumentException("Book ID cannot be null");
    }
    return Optional.ofNullable(books.get(bookId));
}

现在,findBookById 方法返回一个 Optional<Book> 对象,即使没有找到对应的书籍,也会返回一个表示没有值的 Optional.empty()

4. 使用 Optional 和默认值

最后,我们来看一下如何在客户端代码中安全地使用这个服务:

public class BookClient {
    private final BookService bookService = new BookService();

    public void displayBookInfo(Long bookId) {
        Optional<Book> book = bookService.findBookById(bookId);
        book.ifPresentOrElse(
            b -> System.out.printf("Found book: %s by %s\n", b.getTitle(), b.getAuthor()),
            () -> System.out.println("No book found with the given ID.")
        );
    }

    public static void main(String[] args) {
        BookClient client = new BookClient();
        client.displayBookInfo(1L);  // 应该打印出书籍信息
        client.displayBookInfo(3L);  // 应该打印出未找到的信息
    }
}

在这里,我们使用了 OptionalifPresentOrElse 方法来处理两种情况:找到书籍时打印书籍信息,未找到书籍时打印提示信息。

通过这种方式,我们不仅避免了空值带来的运行时错误,还提高了代码的可读性和可维护性。

我们继续扩展这个案例,确保更多的预防式编程实践得到应用。在实际应用中,我们还需要考虑更多的场景和细节,比如异常处理、日志记录等。下面我们将继续完善这个图书管理系统的服务端和客户端代码。

5. 异常处理和日志记录

在实际应用中,我们可能需要更细致地处理异常情况,并记录相关的日志信息,以便于后续的调试和监控。

更新 BookService

BookService 中添加日志记录,并改进异常处理:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

public class BookService {
    private static final Logger LOGGER = Logger.getLogger(BookService.class.getName());
    private final Map<Long, Book> books = new ConcurrentHashMap<>();

    public BookService() {
        // 初始化一些书籍数据
        books.put(1L, new Book(1L, "Clean Code", "Robert C. Martin", "978-0132350884"));
        books.put(2L, new Book(2L, "Design Patterns", "Erich Gamma", "978-0201633610"));
    }

    /**
     * 根据书籍 ID 查找书籍。
     *
     * @param bookId 书籍的 ID
     * @return 包含书籍信息的 Optional 或空(如果找不到)
     */
    public Optional<Book> findBookById(Long bookId) {
        if (bookId == null) {
            throw new IllegalArgumentException("Book ID cannot be null");
        }
        
        Book book = books.get(bookId);
        if (book == null) {
            LOGGER.log(Level.INFO, "Book not found for ID: " + bookId);
        }
        return Optional.ofNullable(book);
    }
}

这里我们引入了日志记录,当找不到书籍时,会在日志中记录相关信息。

6. 客户端代码优化

在客户端代码中,我们可以增加更多的健壮性处理,比如重试机制、异常捕获等。

更新 BookClient
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;

public class BookClient {
    private static final Logger LOGGER = Logger.getLogger(BookClient.class.getName());
    private final BookService bookService = new BookService();

    public void displayBookInfo(Long bookId) {
        try {
            Optional<Book> book = bookService.findBookById(bookId);
            book.ifPresentOrElse(
                b -> System.out.printf("Found book: %s by %s\n", b.getTitle(), b.getAuthor()),
                () -> System.out.println("No book found with the given ID.")
            );
        } catch (IllegalArgumentException e) {
            LOGGER.log(Level.SEVERE, "Invalid input detected: " + e.getMessage(), e);
            System.err.println("Error: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        BookClient client = new BookClient();
        client.displayBookInfo(1L);  // 应该打印出书籍信息
        client.displayBookInfo(3L);  // 应该打印出未找到的信息
        client.displayBookInfo(null);  // 应该打印出错误信息
    }
}

这里我们增加了对 IllegalArgumentException 的捕获,并记录了详细的错误信息。

7. 单元测试

为了确保我们的代码在各种情况下都能正常工作,我们需要编写单元测试来覆盖不同的场景,包括边界条件和异常情况。

单元测试示例

我们可以使用 JUnit 5 和 Mockito 来编写测试:

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class BookServiceTest {
    @Test
    void shouldFindBookById() {
        // 假设的书籍数据
        Book expectedBook = new Book(1L, "Clean Code", "Robert C. Martin", "978-0132350884");

        // 模拟 BookService
        Map<Long, Book> books = Mockito.mock(Map.class);
        Mockito.when(books.get(1L)).thenReturn(expectedBook);

        BookService bookService = new BookService();
        bookService.books = books;  // 替换原有的 map

        // 断言
        assertEquals(Optional.of(expectedBook), bookService.findBookById(1L));
    }

    @Test
    void shouldThrowExceptionWhenBookIdIsNull() {
        BookService bookService = new BookService();
        assertThrows(IllegalArgumentException.class, () -> bookService.findBookById(null));
    }

    @Test
    void shouldReturnEmptyOptionalWhenBookNotFound() {
        BookService bookService = new BookService();
        assertEquals(Optional.empty(), bookService.findBookById(3L));
    }
}

通过这些测试,我们可以确保 BookService 在面对不同情况时的表现符合预期。

通过这些改进,我们不仅增强了代码的健壮性,还确保了在遇到错误时能够有条不紊地处理,并且通过日志记录和单元测试进一步提高了系统的可靠性和可维护性。

😍😍 海量H5小游戏、微信小游戏、Web casualgame源码😍😍
😍😍试玩地址: https://www.bojiogame.sg😍😍
😍看上哪一款,需要源码的csdn私信我😍

————————————————

​最后我们放松一下眼睛
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2125031.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【js】将串口数据翻译为字符串-含源码

一、背景 通过串口工具获取到的以十六进制表示的字符串数据&#xff0c;按照ascii码转换为字符串。 二、分析 输入应为十六进制的以单个字节为单位的字节与字节之间以空格间隔的字符串。 输出应为完整连续的字符串。 三、实现 3.1按照空格拆解输入字符串为字符数组 var i…

vscode 高效率开发手册

vscode 高效率开发手册 2023.11.17 colin v1.0 文章目录 vscode 高效率开发手册一、统一代码风格二、自定义代码片段三、熟记`vscode`快捷键四、一些有用的系统设置1、焦点变更自动保存2、删除文件中的每行末尾的空格五、一些有用的、高效率插件安装方式:1、自行联网安装;2、…

原生 input 中的 “type=file“ 上传文件

目标&#xff1a;实现文件上传功能 原型图&#xff1a; HTML部分&#xff1a; <div class"invoice-item"><div class"invoice-title">增值税专用发票</div><div class"invoice-box"><el-form-item label"标准…

信息化和精益化应当先做哪个?天行健王春城老师回答你

随着科技的飞速发展&#xff0c;企业面临着来自内部和外部的双重压力。在这样的背景下&#xff0c;信息化和精益化成为了企业提升竞争力的两个重要方向。那么&#xff0c;面对这两个重要的战略方向&#xff0c;企业应该先从哪里入手呢&#xff1f;下面天行健王春城老师就此展开…

(九)Mysql之 【MySQL MHA】

前置资源 一、什么是 MHA MHA&#xff08;MasterHigh Availability&#xff09;是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。 MHA 的出现就是解决MySQL 单点的问题。 MySQL故障切换过程中&#xff0c;MHA能做到0-30秒内自动完成故障切换操作。 MHA能在故障切换的…

洁净区在线环境粒子浮游菌监测取样点的选择知多少?

洁净环境在线监测系统的参考法规标准包括国内外GMP标准、国家标准、国际标准以及洁净室设计和施工规范等。企业应根据自身生产需求和法规要求&#xff0c;选择合适的监测系统和监测方案&#xff0c;确保洁净环境的有效控制和产品质量的安全可靠。 【北京中邦兴业洁净环境在线监…

如何在YoloV8中添加注意力机制(两种方式)

文章目录 概要添加注意力机制流程#添加方式一&#xff1a;将注意力机制添加到额外的一层添加方式二&#xff1a;将注意力机制添加到其中一层&#xff0c;不引入额外的层 概要 提示&#xff1a;这里可以添加技术概要 例如&#xff1a; openAI 的 GPT 大模型的发展历程。 添加…

堡垒机的部署方式与防火墙有何不同

在现代网络安全架构中&#xff0c;堡垒机是关键的安全设备之一&#xff0c;它用于管理和控制服务器的访问权限&#xff0c;保障内部网络的安全性。越来越多的企业选择堡垒机来增强网络安全管理。那么堡垒机该如何部署呢?同时&#xff0c;堡垒机与防火墙有何区别?本文将带您深…

sql中索引查看是否生效

在pg数据库中有多种索引存在&#xff0c;在一般情况下我们取使用普通索引 以下是一些常见导致索引未命中的原因和优化策略 1.如果查询中的条件与索引字段的顺序不匹配&#xff0c;或者索引字段没有完全包含在查询条件中&#xff0c;索引可能不会被使用。 2.在查询中使用函数…

OpenAI 草莓模型即将发布,人工智能变革在即

近日&#xff0c;人工智能领域的领军者 OpenAI 即将推出的 “草莓”&#xff08;Strawberry&#xff09;模型引发了广泛关注和热议。据可靠消息&#xff0c;该模型计划在未来两周内作为 ChatGPT 服务的一部分正式发布。 “草莓” 模型作为 OpenAI 盛传已久的神秘项目&#xff…

判断奇偶数的小妙招

要判断一个数是奇数还是偶数&#xff0c;一般首先想到的都是对2取余&#xff0c;但其实有更高明的算法。 首先咱们要知道一个知识点&#xff1a;偶数的二进制末位为0&#xff0c;奇数的二进制末位为1。 这是进位制本身的规则决定的&#xff0c;二进制是“逢二进一”。如果末位…

【ArcGIS Pro实操第一期】研究区域制图-以粤港澳GBA地区为例

ArcGIS Pro实操第一期&#xff1a;研究区域制图-以粤港澳GBA地区为例 数据准备1 ArcGIS Pro绘制研究区域图1.1 基本处理1.2 导入数据并制图1.3 添加整饰要素 参考 数据准备 DEM高程数据市区边界文件&#xff08;.shp文件&#xff09; 目标成图如下&#xff1a; 1 ArcGIS Pr…

电脑与电脑之间怎么快速传输文件?

若两台电脑在同一局域网&#xff0c;可以使用Windows远程桌面传输文件&#xff0c;或者使用远程看看这款免费的远程桌面软件&#xff0c;它支持在不同的网络之间传输文件&#xff0c;而且速度快、安全性高。 步骤1. 在两台电脑上下载、安装并运行远程看看。 步骤2. 注册一个远…

如何在Windows上搭建并运行DolphinScheduler前后端开发环境

作者&#xff1a;海豚调度研究随笔 编辑整理&#xff1a;曾辉 前言 Apache DolphinScheduler 是一个优秀的分布式调度系统&#xff0c;广泛应用于大数据处理和自动化任务管理中。本文详细介绍了如何在Windows环境下搭建Apache DolphinScheduler的前后端开发环境。 包括从源码…

PowerShell收集信息及绕过PowerShell权限

PowerShell脚本的4种执行权限&#xff1a; Restricted&#xff1a;默认设置&#xff0c;不允许任何script运行 AllSigned&#xff1a;只能运行经过数字证书签名的script RemoteSigned&#xff1a;本地脚本不做限制&#xff0c;网络上下载的script就必须有数字签名 Unrestri…

语言模型与人类反馈的深度融合:Chain of Hindsight技术

人工智能咨询培训老师叶梓 转载标明出处 语言模型在理解和执行指令方面取得了显著成功&#xff0c;但依赖人工标注数据的监督式微调需要大量标记数据&#xff0c;这不仅成本高昂&#xff0c;而且可能限制了模型识别和纠正负面属性或错误能力。另一方面&#xff0c;基于人类反馈…

在职研生活学习--20240908

文章目录 九月八日清晨&#xff0c;我们在鸟鸣声中醒来&#xff0c;精神饱满地迎接大汇演的挑战。上午&#xff0c;我们被分成舞龙队、旗手队、拳队、鼓队四个特色团队进行练习。阳光下&#xff0c;我们挥汗如雨&#xff0c;却乐此不疲。鼓声隆隆&#xff0c;龙舞飞扬&#xff…

使用ESP8266和OLED屏幕实现一个小型电脑性能监控

前言 最近大扫除&#xff0c;发现自己还有几个ESP8266MCU和一个0.96寸的oled小屏幕。又想起最近一直想要买一个屏幕作为性能监控&#xff0c;随机开始自己diy。 硬件&#xff1a; ESP8266 MUColed小屏幕杜邦线可以传输数据的数据线 环境 Windows系统Qt6Arduino Arduino 库…

企业图纸加密软件哪个最好用?10款常用图纸加密软件强力推荐!

在现代企业中&#xff0c;保护图纸和设计文件的安全性至关重要。以下是十款常用且功能强大的图纸加密软件推荐&#xff0c;帮助企业更好地保护其知识产权和敏感数据。 1. 安秉网盾 安秉网盾凭借其强大的加密技术和灵活的权限管理功能&#xff0c;成为企业保护图纸安全的首选。…

源码编译安装python3.12没有ssl模块,python3.12 ModuleNotFoundError: No module named ‘_ssl‘

已经编译安装了opensll 3.3 源码编译安装python3.12没有ssl模块&#xff0c;python3.12 ModuleNotFoundError: No module named ‘_ssl’ 报错如下&#xff1a; [rootlocalhost ~]# python Python 3.12.5 (main, Sep 9 2024, 10:33:15) [GCC 4.8.5 20150623 (Red Hat 4.8.5-4…