代码整洁之道【2】--函数

news2024/11/28 0:47:40

关于函数部分的总结

一、函数只做一件事

函数应该只做一件事、做好这件事、只做这件事。
判断函数是否不止做了一件事,还有一个方法,就是看是否能再拆出一个函数,该函数不仅只是单纯地重新

二、函数尽量不要太长

按照作者的理论,函数长度20行封顶为佳。
我的理解是,函数长度需要跟节的函数只做一件事结合起来,并不需要完全拘泥于20行的限制,只要函数在逻辑层次上不可再分解成新的函数(参见第三条),就可以。

三、每个函数一个抽象层次

要确保函数只做一件事,函数中的语句都要在同一抽象层级上。

我们想要让代码拥有自顶向下的阅读顺序。我们想要让每个函数后面都跟着位于下一抽象层级的函数,这样一来,在查看函数列表时,就能循抽象层级向下阅读了。我把这叫做向下规则。

这里咱举个例子,加入你要对一个字符串进行一些处理(例如,append,substring等),然后再对处理后的字符串进行校验。
良好的实践应该类似如下:

String handledStr = handleStr(username);
validate(handledStr);

不是很优雅的实践类似如下:

String trimmedUserName = username.trim();
String handledStr = trimmedUserName.append("something");
validate(handledStr);

为什么说它不是佳实践呢?因为前两行代码是关于对字符串进行处理的具体操作,应该把它们抽象成一个函数,这个抽象出来的函数是这两个具体操作的上一层概念,和validate方法同一层。

四、switch语句

写出精简的switch语句很难,写出只做一件事的switch语句也很难,它天生就要做N件事。我们无法避开switch语句,不过还是可以确保每个switch都放在较低的抽象层级,而且永远不重复。

利用多态实现switch的优化:

假设有下面的需求:根据雇员类型计算薪资。

public Money calculatePay(Employee e) throws InvalidEmployeeType{
    switch (e.type){
        case COMMISSIONED:
            return calculateCommissionedPay(e);
        case HOURLY:
            return calculateHourlyPay(e);
        case SALARIED:
            return calculateSalariedPay(e);
        default:
            throw new InvalidEmployeeType(e.type);
    }
}

这里仅仅依赖了雇员类型一种操作,就有好几个问题:

  1. 函数太长,当有新的雇员类型,还会更长。
  2. 违反开闭原则(OCP原则),每添加新类型,就必须修改它。
  3. 违反了单一权责原则,它做了多件事情。

更麻烦的是:到处都有类似的调用函数(传入的参数类似)。
比如可能多处调用
isPayday(Employee e, Date date);

deliverPay(Employee e, Money pay);

下面我们针对这样的问题进行优化。

对每个类都会有同样的操作,比如isPayday(), deliverPay()等,不如把类的行为抽象出来到一个抽象类Employee中。在抽象工厂中使用switch语句为Employee的派生物创建适当的实体。

对于switch语句,我们的规矩是如果只出现一次,用于创建多态对象,而且隐藏在某个继承关系中,在其他系统看不到,就还能容忍。

public abstract class Employee{
    public abstract boolean isPayday();
    public abstract Money calculatePay();
    public abstract void deliverPay(Money pay);
}

public interface EmployeeFactory{
    public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
}

public class EmployeeFactoryImpl implements EmployeeFactory{
    public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType{
        switch (r.type){
            case COMMISSIONED:
                return new CommissionedEmployee(r);
            case HOURLY:
                return new HourlyEmployee(r);
            case SALARIED:
                return new SalariedEmployee(r);
            default:
                throw new InvalidEmployeeType(r.type);
        }
    }
}

用优化后的代码,再有新的类型加入时,业务程序是不用修改的,因为类型已隐藏在了抽象类中,返回给业务的都是抽象的Employee,无需考虑类型的变化,只是调用抽象类的方法即可。我们需要改动的只是再创建一个抽象类的实体类,在EmployeeFactoryImpl中多加一个switch分支。

五、使用描述性的名称

长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说清其功用的名称。

六、函数参数

参数数量越少越好。 尽量不要有输出参数,而是将输出设置为返回值。 如果参数较多的时候可以考虑使用类进行封装。

七、无副作用

副作用是一种谎言。函数承诺只做一件事,但还是会做其他被藏起来的事。有时,它会对自己类中的变量做出没有预料到的改动。有时,它会把变量搞成向函数传递的参数或是系统全局变量。无论哪种情况,都是具有破坏性的,会导致古怪的时序性耦合及顺序依赖。

例如如下代码,改函数使用标准算法来匹配userName和passWord,如果匹配成功,返回true,如果匹配失败,返回false,但是它会有副作用:

副作用就在于对Session.initialize()调用。checkPassword函数,顾名思义,就是用来检查密码的。该名称并没有暗示它会初始化该次会话。所以,当某个误信了函数名的调用者想要检查用户有效性时,就会冒着抹除现有会话数据的风险。也就是说,这个副作用造成了一次时序性耦合。

八、分隔指令与询问

这实际上内生的包含于一个函数只做一件事的要求中,但是还是有必要单独指出。函数要么做什么事,要么回答什么事,但二者不可兼得。

函数应该修改某对象的状态,或者返回该对象的有关信息。两样都干会导致混乱。

举个例子:

if (set("username", "unclebob"))...

上面这个语句会让人迷惑:它是询问username属性之前是否已经被设置为unclebob了?还是在问username属性是否成功被设置为unclebob呢?从这行调用很难判断其含义。

要解决这个问题,可以按如下方式改造,防止混淆的发生:

if (attributeExists("username")) {
    setAttribute("username", "unclebob");
}

九、使用异常替代返回错误码

可减少过度嵌套(判断多种错误码及内层错误码)。
可减少对错误码枚举类的过度依赖(当修改了错误码枚举类时,所有依赖这个枚举类的其他类都得重新编译和部署)。

十、别重复自己

如果你发现某两个函数用到了相同甚至相近的代码块应该迅速思考是不是可以将其抽取成单独的函数。 重复就是万恶之源。

十一、结构化函数

Dijkstra认为,每个函数、每个代码块都应该只有一个入口一个出口。这意味着每个函数只能有一个return语句,循环中不能有break或continue,而且永远不能出现goto。 事实上,当代码相对较短的时候,适当多几个return、break、continue无伤大雅。当代码冗长时,这样的规则才能够发挥出其效力来。

十二、如何写出好的函数?

写代码和写别的东西很像,在写文章或者论文时,你先想些什么就写什么,然后再打磨它。 初稿也许粗陋无序,你就斟酌推敲,直到达到你心中的样子。

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

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

相关文章

MQ之————如何保证消息的可靠性

MQ之保证消息的可靠性 1.消费端消息可靠性保证: 1.1 消息确认(Acknowledgements): 消费者在接收到消息后,默认情况下RabbitMQ会自动确认消息(autoAcktrue)。为保证消息可靠性,可以…

VMware启动显示“打开虚拟机时出错: 获取该虚拟机的所有权失败”

提示框(忘截图了)里提示目录C:\Users\mosep\Documents\Virtual Machines\VM-Win10 x64\中的某个文件(在我这里好像是VM-Win10 x64.vmx,VM-Win10 x64是我给虚拟机取的名字)在被使用中。 找到这个目录,删除.…

【Linux】虚拟化技术docker搭建SuitoCRM系统及汉化

CRM系统 CRM(Customer Relationship Management,客户关系管理)系统是一种用于管理和优化企业与客户关系的软件工具。在商业竞争激烈的现代社会中,CRM系统已成为许多企业提高销售、增强客户满意度和实现持续增长的重要工具。 搭建…

NumPy进阶(二)

2. NumPy进阶(二) 2.1 Numpy数组操作 2.1.1 添加元素 numpy.append 函数在数组的末尾添加值。 追加操作会分配整个数组,并把原来的数组复制到新数组中 注意: 插入的维度要保证所有数组的长度是相同的如果没有指定轴,数组会被扁平处理 ndarr…

MOMENT: A Family of Open Time-series Foundation Models

MOMENT: A Family of Open Time-series Foundation Models PDF: https://arxiv.org/pdf/2402.03885.pdf Code: https://anonymous.4open.science/r/BETT-773F/README.md 1 概述 MOMENT是一个用于通用时间序列分析的开源基础模型系列。由于缺少大型公共时间序列存储库、时间序…

matlab:有限差分求解纳维尔(Navier)边界的双调和(Biharmonic)方程,边值为零

我们考虑如下形式的双调和方程的数值解 其中,Ω是欧氏空间中的多边形或多面体域,在其中,d为维度,具有分段利普希茨边界,满足内部锥条件,f(x) ∈ L2(Ω)是给定的函数,∆是标准的拉普拉斯算子。算…

JVM规范中的运行时数据区

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏:每天一个知识点 ✨特色专栏&#xff1a…

YOLOv9改进策略 :卷积魔改 | 变形条状卷积,魔改DCNv3二次创新

💡💡💡本文独家改进: 变形条状卷积,DCNv3改进版本,不降低精度的前提下相比较DCNv3大幅度运算速度 💡💡💡强烈推荐:先到先得,paper级创新,直接使用; 💡💡💡创新点:1)去掉DCNv3中的Mask;2)空间域上的双线性插值转改为轴上的线性插值; 💡💡💡…

QT windeployqt打包出现无法正常启动问题

QT 通过windeployqt 打包后出现的问题 原因QT构建选择的是64位的 但是windows下运行的却是32位的 步骤打开32的所在路径 一般在上一级目录会有安装好的64位的MSVC工具 运行打包即可

装饰建材商城网满足家装行业需求,改变传统装修市场

装饰建材商城网满足家装行业需求,改变传统装修市场 随着国内楼市的火爆发展,家装行业可谓是炙手可热。人们关于家装的需求也开始从过去简单的宜居,开始向多元化需求转变,如环保、健康、安全、绿色、时尚等等。加上互联网的快速发展…

电能质量测试仪的功能特点

武汉凯迪正大电能质量测试仪功能特点 1、多通道测量:4个电压通道、4个电流通道同时测量。 2、电气参数测量:可同时测量电压幅值、电流幅值、相位、频率、有功功率、无功功率、功率因数等参数; 3、可测量2-64次的电压谐波和电流谐波含量&am…

C语言:指针详解(1)

目录 一、内存和地址 1.内存 2.究竟该如何理解编址 二、指针变量和地址 1.取地址操作符(&) 2.解引用操作符(*) 3.指针变量的大小 三、指针变量类型的意义 1.指针的解引用 2.指针-整数 3.void*指针 四、const修饰指针 1.const修饰变量 2.const修饰指针变量 五…

【计算机毕业设计】企业仓储管理系统——后附源码

🎉**欢迎来到我的技术世界!**🎉 📘 博主小档案: 一名来自世界500强的资深程序媛,毕业于国内知名985高校。 🔧 技术专长: 在深度学习任务中展现出卓越的能力,包括但不限于…

如何利用在线仿真软件提高教学质量?

在教育技术迅速发展的今天,老师们面临着一个共同的挑战:如何有效地利用新兴技术提高教学质量。特别是在科学、技术、工程和数学(STEM)教育领域,实践性和互动性是学习过程中不可或缺的元素。本文将深入探讨在线仿真软件…

[大模型]ChatGLM3-6B Code Interpreter

ChatGLM3-6B Code Interpreter 请注意,本项目需要 Python 3.10 或更高版本。 环境准备 由于项目需要python 3.10或更高版本,所以我们在在autodl平台中租一个3090等24G显存的显卡机器,如下图所示镜像选择Miniconda–>conda3–>3.10(ubu…

护眼灯值不值得买?收获好评最多的护眼灯十大品牌推荐

如今,我们可以清晰地观察到越来越多的人很早就戴上眼镜的现象。这可能是由于频繁接触电子产品,长时间的学习,或处于不良的光线环境下造成的。不论原因何在,我们都意识到创造良好的光线环境对保护视力至关重要。尽管一些人对市面上…

springboot3使用自定义注解+AOP+redis优雅实现防重复提交

⛰️个人主页: 蒾酒 🔥系列专栏:《spring boot实战》 🌊山高路远,行路漫漫,终有归途 目录 写在前面 实现思路 实现步骤 1.定义防重复提交注解 2.编写一个切面去发现该注解然后执行防重复提交逻辑 3.测试 …

Spring Cloud系列(二):Eureka Server应用

系列文章 Spring Cloud系列(一):Spirng Cloud变化 Spring Cloud系列(二):Eureka Server应用 目录 前言 注册中心对比 Nacos Zookeeper Consul 搭建服务 准备 搭建 搭建父模块 搭建Server模块 启动服务 测试 其他 前言 前面针对新版本的变化有了…

【SCI绘图】【曲线图系列1 python】绘制扫描点平滑曲线图

SCI,CCF,EI及核心期刊绘图宝典,爆款持续更新,助力科研! 本期分享: 【SCI绘图】【曲线图1 python】绘制扫描点平滑曲线图 1.环境准备 python 3 import numpy as np import pandas as pd import proplot …

镗床工作台开槽的作用

镗床工作台开槽的作用主要有以下几点: 改善工作台的刚度和稳定性:开槽可以增加工作台的刚度,使其能够承受更大的切削力和振动力,提高工作台的稳定性。 方便工件夹紧和定位:开槽可用于夹紧和定位工件,使其能…