厌倦了混乱的代码?掌握编写干净代码库的艺术

news2024/9/17 8:33:23

对于入门的开发人员来说,虽然克服了最初的障碍,学会了编程,找到了理想的工作。但其编程旅程并没有就此结束。他们面临真正的挑战:如何编写更好的代码。这不仅仅是为了完善功能,还要编写出经得起时间考验的优雅、可维护的代码。

在设计糟糕的软件系统中,开发人员在后台就像迷失在一个没有地图导航的城市里一样。这些系统往往笨重、低效且令人沮丧。

开发人员可以通过设计更好的以用户为中心、高效、简单、灵活的系统来改变这种状况。他们可以使用函数、变量、类和注释来编写“不要重复自己”(DRY)和模块化的代码,设计为人们服务的系统,而不是相反。

因此开发人员的选择是明确的:编写赋能的代码而不是阻碍的代码,构建让开发人员茁壮成长的代码体系。

代码库的挑战

想象一下,如果你是一名销售主管,采用了一款新的应用程序,旨在简化工作流程,并最大限度地提高潜在客户的转化率。这款应用程序由SmartReach API提供支持,可以实时了解潜在客户,并有可能获得奖励。

但感觉这款应用程序有些问题,其代码缺乏清晰度和结构,引起了人们对其准确性和功能的担忧。分析代码变成了一项令人困惑的任务,阻碍了有效使用应用程序的能力。

与其纠结于晦涩难懂的代码行,不如以一种清晰而结构化的方式来分解代码。这将使开发人员能够识别潜在的错误,并确保应用程序顺利运行,最大限度地提高生产力和获得奖励的潜力。

class ApiCall {
 def getData(pn: Int, v: Int) = Action.async(parse.json) { request =>
 ws.url(s"https://api.smartreach.io/api/v$v/prospects?page=$pn")
 .addHttpHeaders(
 "X-API-KEY" -> "API_KEY"
 )
 .get()
 .flatMap(r => {
 if (r.status == 200) {
 Res.Success("Success!", (response.json \ "data").as[JsValue])
 } else {
 Res.ServerError("Error!",new Exception((response.json \ "message").as[String]))
 }

 })
   }
}

我们将重构一个用Scala编写但适用于一般编码的示例销售应用程序。那么目标是什么?是将令人头疼的混乱代码变成一部清晰可读的杰作。

以下分解重构过程,解释每个步骤并展示其积极影响。让我们一起创造激励和赋能的代码!

代码重构

命名约定和代码注释

如今,我们都在关注快速消息,但在编写代码时,这并不奏效。

为变量、类、函数和对象等使用清晰的名称是非常重要的。如果让同事帮助检查或修改代码,别忘了在代码中编写一些注释。这可能看起来没什么大不了的,但当你或你的同事试图解决出现的代码问题时,这些注释将提供很大的帮助。

让我们看看遵循这些规则之后编写代码的结果:

class ProspectsApiGet {
 def getProspectListFromSmartReach(
 page_number: Int,
 version: Int
 ) = Action.async(parse.json) { request =>
 //public API of SmartReach to get the prospect list by page number
 //smartreach.io/api_docs#get-prospects
 ws.url(s"https://api.smartreach.io/api/v$version/prospects?page=$page_number") 
 .addHttpHeaders(
 "X-API-KEY" -> "API_KEY"
 )
 .get()
 .flatMap(response => {
 val status = response.status

 if (status == 200) { 
 //if the API call to SmartReach was success
   /*
 {
 "status": string,
 "message": string,
 "data": {
 "prospects": [*List of prospects*]
 }
 }
 */
 val prospectList: List[JsValue] = (response.json \ "data" \\ "prospects").as[List[JsValue]]
 Res.Success("Success!", prospectList)
 } else {
 // error message
 //smartreach.io/api_docs#errors
 val errorMessage: Exception = new Exception((response.json \ "message").as[String]) 
 Res.ServerError("Error", errorMessage) // sending error
 }
 })
 }
}

如果仔细观察,错误就会隐藏在代码中! 对潜在客户的分析不太正确。但是,通过提供更多的注释和使用更好的名称,可以避免这些错误。

代码库组织和模块化

直到现在都一切顺利。编写的代码很好,应用程序也很简单。既然代码运行良好并且更易于阅读,那么让我们来处理下一个挑战。

考虑在这里引入一个筛选系统。这会让事情变得更加复杂。我们真正需要的是一个更有组织的代码结构。

为了使其实现模块化,我们将代码分成更小的模块。但是如何确定每个模块的位置呢?

(1)目录结构

目录是代码的总部,本质上是一个将所有内容放在一起的文件夹。如果创建一个新文件夹,这样就有了一个目录。

在这个目录中,可以存储代码文件或创建其他子目录。在我们的例子中则选择子目录。将代码分成四个部分:模型、数据访问对象(DAO)、服务和控制器。

值得注意的是,目录结构可能会根据企业偏好或应用程序的特定需求而有所不同。你可以根据最适合企业或应用的内容来定制。

(2)模型

当我们谈论编码中的模型时,本质上是在谈论用来构建和管理数据的框架。例如在这个例子的场景中,前景模型充当了蓝图,概述了代表系统内前景的特定特征和行为。

在这个模型中,我们定义了潜在客户拥有的属性——可能是姓名、联系方式或任何其他相关信息。这不仅仅是存储数据,还是有关以一种对应用程序有效地交互和操作有意义的组织方式。

(3)数据访问对象(DAO)

这个对象被恰当地命名为数据访问对象(DAO),充当从数据库或第三方API获取数据的桥梁。

避免在这些文件中添加复杂的逻辑是至关重要的;它们应该只专注于处理输入和输出操作。当我们谈论IO操作时,指的是与外部系统的交互,其中故障的可能性更高。因此,在这里实现保障措施对于处理意外问题至关重要。

在Scala编程语言中,我们利用Monad(特别是Futures)来有效地管理和处理潜在的故障。这些工具有助于捕获和管理IO操作过程中可能发生的故障。

数据访问对象(DAO)的主要目标是从数据源检索数据,然后将其组织到适当的模型中,以便进一步处理和利用。

class SmartReachAPIService {
 def getSmartReachProspects(
 page_number: Int,
 Version: Int
 )(implicit ws: WSClient,ec: ExecutionContext): Future[List[SmartReachProspect]] = {
 //public API from SmartReach to get the prospects by page number
 //smartreach.io/api_docs#get-prospects
 ws.url(s"https://api.smartreach.io/api/v$version/prospects?page=$page_number") 
 .addHttpHeaders(
 "X-API-KEY" -> "API_KEY"
 )
 .get()
 .flatMap(response => {
 val status = response.status
 if (status == 200) { 
 //checking if the API call to SmartReach was success

 /*
 {
 "status": string,
 "message": string,
 "data": {
 "prospects": [*List of prospects*]
 }
 }
 */
 val prospects: List[SmartReachProspect] = (response.json \ "data" \\ "prospects").as[List[SmartReachProspect]]
 prospects
 } else {
 //error message
 //smartreach.io/api_docs#errors
 val errorMessage: Exception = new Exception((response.json \ "message").as[String]) 
 throw errorMessage
 }
 })
 }
}

(4)服务

这里是我们系统的核心——业务逻辑位于这一层。在这里将实现一种筛选机制,展示如何毫不费力地在这部分代码库中引入额外的功能。

这个部分编排驱动应用程序的核心操作和规则。在这里,根据业务需求定义应该如何处理、操作和转换数据。让添加新特性或逻辑变得相对简单,可以轻松地扩展和增强系统的功能。

class SRProspectService {
 val smartReachAPIService = new SmartReachAPIService
 def filterOnlyInterestedProspects(
 prospects: List[SmartReachProspect]
 ): List[SmartReachProspect] = {
 prospects.filter(p => p.prospect_category == "interested")
 }
 def getInterestedProspects(
 page_number: Int,
 version: Int
 )(implicit ws: WSClient,ec: ExecutionContext): Future[List[SmartReachProspect]] = {
 val allProspects: Future[List[SmartReachProspect]] = smartReachAPIService.getSmartReachProspects(page_number = page_number, version = version)
 allProspects.map{list_of_prospects => 
 filterOnlyInterestedProspects(prospects = list_of_prospects)
 }
 }
}

(5)控制器

在这一层,我们与API建立直接连接——这一层充当网关。

它充当接口,通过我们的API接收来自前端或第三方用户的请求。在接到这些请求之后,收集所有必要的数据,并在处理后处理响应。

保持关注点分离是至关重要的。因此避免在这个层中实现逻辑。与其相反,该层侧重于管理传入请求流,并将它们引导到实际处理和业务逻辑发生的适当服务层。

class ProspectController {
 val prospectService = new SRProspectService
 def getInterestedProspects(
 page_number: Int
 ) = Action.async(parse.json) { request =>
 prospectService
 .getInterestedProspects(page_number = page_number, version = 1)
 .map{ intrested_prospects => 
 Res.Success("Success", intrested_prospects)
 }
 .recover{ errorMessage => 
 Res.ServerError("Error", errorMessage) // sending error to front end 
 }
 }

}

我们改进的代码库具有更高的清洁度和更强的可管理性,促进了更高效的重构工作。

此外,我们还建立了不同的标记,用于合并逻辑、执行数据库操作或无缝集成新颖的第三方API。这些清晰的划分简化了在系统中扩展功能和适应未来增强的过程。

测试和质量保证

测试可能看起来是重复的,但它的重要性怎么强调都不为过,尤其是在熟练使用的情况下,无需为重新编码而烦恼。

让我们更深入地了解构建健壮Spec文件的指导原则。

1.覆盖是关键:在为特定函数构建规范时,确保这些规范涉及该函数中的每一行代码是至关重要的。实现全面覆盖保证了在测试过程中对功能内的所有路径和场景进行仔细检查。

2.失败优先测试:Spec文件的主要作用是检查代码在不同情况下的行为。为了实现这一点,包含一系列测试用例是至关重要的,尤其是那些模拟潜在故障场景的测试用例。确保可靠的错误处理需要对所有可预见的故障实例进行测试。

3.接受集成测试:虽然单元测试擅长于评估代码的逻辑方面,但它们可能会无意中忽略与输入/输出操作相关的潜在问题。为了解决这个问题,集成和执行彻底的集成测试变得必不可少。这些测试模拟真实世界的场景,验证代码在与外部系统或资源交互时的行为。

协作与团队合作

有一条要记住的黄金法则:在遇到难以解决的问题时,可以寻求他人帮助。他人的帮助对于你自身能力实现指数增长至关重要。

创建一个健壮的代码库不是一项单独的任务,而是需要团队的努力。尽管开发人员的职业表面上看起来是内向的,但协作构成了编码的支柱。这种合作超越了企业的界限。

GitHub、LeetCode和StackOverflow等平台展示了编码社区的活跃和社交性质。如果你遇到一个以前没有遇到的问题,很可能别人也遇到过。因此请始终对寻求帮助持开放态度。

代码文档的最佳实践

无论是公共API还是内部代码,可靠的文档是游戏规则的改变者。不过,现在将重点放在了编码方面。

确保一流的文档实践不仅仅是为了简化开发人员的入职过程,而是为了向开发团队提供一个对代码库有清晰见解的宝库。这种清晰性推动了学习,使每个参与者都受益。

预先编码和研究

这是启动项目的所有要素的起点——从系统设计到指向相关文档的关键链接。

在预编码文档中,开发人员不仅可以找到新的表结构和修改,还可以找到指向第三方文档的有价值的链接。需要记住的是,充分的准备为成功奠定了基础,将会事半功倍!

编码期间

这是开发人员在代码中嵌入注释的地方。这些注释作为指南,揭示了代码功能背后的复杂性和意图。

事后编码

这是有效利用代码的指导中心,也是将来修改代码的指南。它是引导用户使用代码的指南,并概述了后续修改代码的路径。

结论

优秀的代码遵循可靠的命名约定,保持模块化,遵循“不要重复自己”(DRY)原则,确保可读性,并经过彻底的测试。编写这样的代码需要混合文档、协作以及为编码人员提供帮助。

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

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

相关文章

HTML5 Canvas 限定文本区域大小,文字自动换行,自动缩放

<!DOCTYPE html> <html> <body><h1>HTML5 Canvas 限定文本展示范围、自动计算缩放字体大小</h1><div id"tips">0</div> <div id"content">良田千顷不过一日三餐广厦万间只睡卧榻三尺良田千顷不过一日三餐…

发电机组启动前的准备和检查注意事项

发电机组启动前的准备&#xff1a; 1.检查润滑油的油位、 冷却液液位、燃油量&#xff1b; 2.检查机的供油、润滑、冷却等系统各个管路及接头有无漏油漏水现象&#xff1b; 3.检查电气线路有无破皮等漏电隐患&#xff0c;接地线电气线路是否松动&#xff0c;机组与基础的连接是…

MES系统中的手动排产和自动排产-助力生产效率

企业在排产管理中面临的问题&#xff1a; 大多数的企业在调度排产过程中&#xff0c;都会遇到以下问题。首先是插单非常的多&#xff0c;计划调整困难&#xff0c;会经常性的发生原材料、零部件的备货不足。计划按MRP或库存展示计算出需求后将产生大量工单&#xff0c;这些工单…

transformer 最简单学习1 输入层embeddings layer,词向量的生成和位置编码

词向量的生成可以通过嵌入层&#xff08;Embedding Layer&#xff09;来完成。嵌入层是神经网络中的一种常用层&#xff0c;用于将离散的词索引转换为密集的词向量。以下是一个典型的步骤&#xff1a; 建立词表&#xff1a;首先&#xff0c;需要从训练数据中收集所有的词汇&…

vue 常用库

vue-cropper 一个优雅的图片裁剪插件 dayjs Day.js 是一个轻量的处理时间和日期的 JavaScript 库&#xff0c;和 Moment.js 的 API 设计保持完全一样 NutUI-Bingo 基于 NutUI 的抽奖组件库&#xff0c;助力营销活动和小游戏场景。

java面试题之mybatis篇

什么是ORM&#xff1f; ORM&#xff08;Object/Relational Mapping&#xff09;即对象关系映射&#xff0c;是一种数据持久化技术。它在对象模型和关系型数据库直接建立起对应关系&#xff0c;并且提供一种机制&#xff0c;通过JavaBean对象去操作数据库表的数据。 MyBatis通过…

内容检索(2024.02.23)

随着创作数量的增加&#xff0c;博客文章所涉及的内容越来越庞杂&#xff0c;为了更为方便地阅读&#xff0c;后续更新发布的文章将陆续在此汇总并附上原文链接&#xff0c;感兴趣的小伙伴们可持续关注文章发布动态&#xff01; 本期更新内容&#xff1a; 1. 电磁兼容理论与实…

C语言——指针——第2篇——(第20篇)

坚持就是胜利 文章目录 一、指针和数组二、二级指针1、什么是 二级指针&#xff1f;2、二级指针 解引用 三、指针数组模拟二维数组 一、指针和数组 问&#xff08;1&#xff09;&#xff1a;指针和数组之间是什么关系呢&#xff1f; 答&#xff1a;指针变量就是指针变量&…

【spring】 ApplicationListener的使用及原理简析

文章目录 使用示例&#xff1a;原理简析&#xff1a; 前言&#xff1a;ApplicationListener 是spring提供的一个监听器&#xff0c;它可以实现一个简单的发布-订阅功能&#xff0c;用有点外行但最简单通俗的话来解释&#xff1a;监听到主业务在执行到了某个节点之后&#xff0c…

GitHub热门项目之Memos 打造私有备忘录

效果 1. 写备忘录或简单笔记&#xff0c;支持Markdown 2. 时间线 3. 探索可以看到其他用户公开的内容 项目地址 usememos/memos&#xff1a;一种开源的轻量级笔记服务。轻松捕捉和分享您的伟大想法。 (github.com)https://github.com/usememos/memos 体验地址 Memoshttp://…

精通Django模板(模板语法、继承、融合与Jinja2语法的应用指南)

模板&#xff1a; 基础知识&#xff1a; ​ 在Django框架中&#xff0c;模板是可以帮助开发者快速⽣成呈现给⽤户⻚⾯的⼯具模板的设计⽅式实现了我们MVT中VT的解耦(M: Model, V:View, T:Template)&#xff0c;VT有着N:M的关系&#xff0c;⼀个V可以调⽤任意T&#xff0c;⼀个…

【操作系统】磁盘文件管理系统

实验六 磁盘文件管理的模拟实现 实验目的 文件系统是操作系统中用来存储和管理信息的机构&#xff0c;具有按名存取的功能&#xff0c;不仅能方便用户对信息的使用&#xff0c;也有效提高了信息的安全性。本实验模拟文件系统的目录结构&#xff0c;并在此基础上实现文件的各种…

【服务器数据恢复】FreeNAS+ESXi虚拟机数据恢复案例

服务器数据恢复环境&#xff1a; 一台服务器通过FreeNAS&#xff08;本案例使用的是UFS2文件系统&#xff09;实现iSCSI存储&#xff0c;整个UFS2文件系统作为一个文件挂载到ESXi虚拟化系统&#xff08;安装在另外2台服务器上&#xff09;上。该虚拟化系统一共有5台虚拟机&…

Elasticsearch从入门到精通-01认识Elasticsearch

Elasticsearch从入门到精通-01认识Elasticsearch &#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是程序员行走的鱼 &#x1f342;博主从本篇正式开始ES学习&#xff0c;希望小伙伴可以一起探讨 &#x1f4d6; 本篇主要介绍和大家一块简单认识下ES并了解ES中的主要角色…

H5获取手机相机或相册图片两种方式-Android通过webview传递多张照片给H5

需求目的&#xff1a; 手机机通过webView展示H5网页&#xff0c;在特殊场景下&#xff0c;需要使用相机拍照或者从相册获取照片&#xff0c;上传后台。 完整流程效果&#xff1a; 如下图 一、H5界面样例代码 使用html文件格式&#xff0c;文件直接打开就可以展示布局&#…

爬虫知识--03

数据存mysql import requests from bs4 import BeautifulSoup import pymysql# 链接数据库pymysql conn pymysql.connect(userroot,password"JIAJIA",host127.0.0.1,databasecnblogs,port3306, ) cursor conn.cursor() cursor conn.cursor()# 爬数据 res request…

MaxScale实现mysql8读写分离

MaxScale 实验环境 中间件192.168.150.24MaxScale 22.08.4主服务器192.168.150.21mysql 8.0.30从服务器192.168.150.22mysql 8.0.30从服务器192.168.150.23mysql 8.0.30 读写分离基于主从同步 1.先实现数据库主从同步 基于gtid的主从同步配置 主库配置 # tail -3 /etc/my.…

Encoder-decoder 与Decoder-only 模型之间的使用区别

承接上文&#xff1a;Transformer Encoder-Decoer 结构回顾 笔者以huggingface T5 transformer 对encoder-decoder 模型进行了简单的回顾。 由于笔者最近使用decoder-only模型时发现&#xff0c;其使用细节和encoder-decoder有着非常大的区别&#xff1b;而huggingface的接口为…

【vue】provide/inject

provide/ inject这对选项需要一起使用&#xff0c;以允许一个祖先组件向其所有子孙后代注入一个依赖&#xff0c;不论组件层次有多深&#xff0c;并在起上下游关系成立的时间里始终生效。 通途点来讲可以用来实现隔代传值&#xff0c;传统的props只能父传子&#xff0c;而 prov…

Vue3实现页面顶部进度条

Vue3页面增加进度条 新建进度条组件新建bar.ts导航守卫中使用 Vue3项目使用导航守卫给页面增加进度条 新建进度条组件 loadingBar.vue <template><div class"wraps"><div ref"bar" class"bar"></div></div> <…