重构,第一个案例(C++版)——最初的程序

一个C++程序重构的例子——糟糕的代码 由于工作中常用c++的原因,在看《Refactoring — Improving the Design of Exsiting Code》这本书时,将java的例子写成了c++程序,略做总结,以深理解。 重构的习惯与思想,跟语言无关。但c++是一个难掌控的语言,记下这些优化改进,希望上进的c++ programmer们,能实现日积跬步,终至千里的梦想。 一、待优化的程序 程序描述: (1) 阅读详情

之前写过一篇文章找出那些代码里的坏味道吧——《重构》笔记(一)。简单总结了一下《重构》这本书中的重点以及作者总结的“代码里的坏味道”。

这样的总结比较抽象,这里我将写一个系列文章,主要想通过案例来让大家具体的感受一下小步重构的魅力。使用的例子也是Martin Fowler《重构》一书中第一章的案例。不过书中案例是用Java写的,这里我将其改为了C++程序。希望这样对C++程序员能更好的理解重构。

起点

考虑实现一个租碟店需要用的程序,需求是:

  • 计算每一位顾客的消费金额并打印详单。
  • 操作者输入:顾客租了哪些影片、租期多长,程序根据租赁时间和影片类型算出费用。
  • 影片分为三类:普通片、儿童片和新片。
  • 为会员计算积分,积分会根据影片类型是否为新片而有不同。

这是程序的一个简单实现的UML类图。
本例第一个UML类图,只显示最重要的特性。

下面是UML类图中各个类的代码:

Movie(影片)

Movie只是一个简单的纯数据类。

enum PriceCode
{
	REGULAR = 0,
	NEW_RELEASE = 1, 
	CHILDREN = 2
};

class Movie 
{
public:
	Movie() {}
	Movie(const string& title, int price_code) {
		title_ = title;
		price_code_ = price_code;
	}

	int GetPriceCode() { return price_code_; }
	void SetPriceCode(int price_code) { price_code_ = price_code; }
	string GetTitle() { return title_; }

private:
	string title_;
	int price_code_;
};
Rental(租赁)

Rental表示某个顾客租了一部影片。

class Rental
{
public:
	Rental() {}
	Rental(const Movie& movie, int days_rentaed) {
		movie_ = movie;
		days_rented_ = days_rentaed;
	}

	int GetDaysRented() { return days_rented_; }
	Movie GetMovie() { return movie_; }

private:
	Movie movie_;
	int days_rented_;
};
Customer(顾客)

Customer类用来表示顾客。就像其他类一样,它也拥有数据和相应的访问函数。同时Customer还提供了一个用于生成详单的函数statement()。
statement() 时序图

class Customer
{
public:
	Customer(const string& name) {
		name_ = name;
	}

	void AddRental (const Rental& arg) { rentals_.emplace_back(arg); }
	string GetName() { return name_; }
	string Statement() {
		double total_amount = 0;
		int frequent_renter_points = 0;
		string result = "Rental Record for " + GetName() + "\n";
		for (auto& each : rentals_) {
			double this_amount = 0;
			
			// determine amounts for each line
			switch (each.GetMovie().GetPriceCode())
			{
			case PriceCode::REGULAR:
				this_amount += 2;
				if (each.GetDaysRented() > 2)
					this_amount += (each.GetDaysRented() - 2) * 1.5;
				break;
			case PriceCode::NEW_RELEASE:
				this_amount += each.GetDaysRented() * 3;
				break;
			case PriceCode::CHILDREN:
				this_amount += 1.5;
				if (each.GetDaysRented() > 3)
					this_amount += (each.GetDaysRented() - 3) * 1.5;
				break;
			}

			// add frequent renter points
			frequent_renter_points ++;
			// add bonus for a two day new release rental
			if (each.GetMovie().GetPriceCode() == PriceCode::NEW_RELEASE &&
				each.GetDaysRented() > 1)
				frequent_renter_points++;

			
			// show figures for this rental
			result += "\t" + each.GetMovie().GetTitle() + "\t" +
				to_string(this_amount) + "\n";
			total_amount += this_amount;
		}

		// add footer lines
		result += "Amount owed is " + to_string(total_amount) + "\n";
		result += "You earned " + to_string(frequent_renter_points) +
			" frequent renter points";
		
		return result;
	}

private:

	string name_;
	vector<Rental> rentals_;
};

对于这个我们最初版本的程序,首先,可以肯定的是它是能正常运行的,没有bug。如果这是个测试程序,或者这个程序就这样了,以后不会再修改它,那么我们甚至可以说它是完美的,因为我们快速的实现了我们想要的功能,并且没有bug。但是我们很容易的可以“闻到”《重构》这本书中总结的——Long Method(过长函数)这个“代码的坏味道”。当所有的业务逻辑都堆积在这个长函数中时,就会使得这个程序难以修改,难以扩展,并且可能会越变越糟糕。这时候,很明显的我们需要重构了。

如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便地达成目的,那就先重构那个程序,使特性的添加比较容易进行,然后再添加特性。

重构第一步

每当我要进行重构的时候,第一个步骤永远相同:我得为即将修改的代码建立一组可靠的测试环境。

这里作者强调了单元测试的重要性,因为人总是可能犯错的,重构有可能引起意想不到的bug,所以对于原有功能的接口部分进行单元测试就显得很有必要了。只是,在现实的项目中大部分时候因为需求的紧急,我们都忽略了这个。这里,我就在main函数中加了一段简单的测试代码。

int main() {
	Movie movie1{ "Pulp Fiction", PriceCode::REGULAR };
	Movie movie2{ "Joker", PriceCode::NEW_RELEASE };
	Movie movie3{ "Peppa Pig", PriceCode::CHILDREN };

	Rental rental1{ movie1, 3 };
	Rental rental2{ movie2, 3 };
	Rental rental3{ movie3, 3 };

	Customer customer{ "Caesar" };
	customer.AddRental(rental1);
	customer.AddRental(rental2);
	customer.AddRental(rental3);
	cout << customer.Statement().c_str() << endl;

	return 0;
}

运行结果如图。之后,每完成一个小步的重构我们都会运行一下测试代码以检测结果是否和最初的结果是一样的,如果有不一样,我们就得检查下重构的代码,看看是不是引入了新的bug。
运行结果

思路总结

  • 重构的第一步总是应该写一段单元测试代码以保证重构不会引入新的bug。

本系列文章

找出那些代码里的坏味道吧——《重构》笔记(一)
重构,第一个案例(C++版)——最初的程序
重构,第一个案例(C++版)——分解并重组Statement()
重构,第一个案例(C++版)——运用多态取代与价格相关的条件逻辑

实践《重构:改善既有代码的设计(第2版)》示例(C++版)(二) 《重构:改善既有代码的设计》C++版本,相互讨论,共同学习,不断成长 阅读详情

相关推荐

别再只写C++ WebServer了!用这3个现代C++库(Boost.Asio、cpp-httplib、seastar)重构你的项目,性能直接起飞

本文探讨了如何利用现代C++库(Boost.Asio、cpp-httplib和seastar)重构传统C++ WebServer项目,显著提升性能和代码质量。通过异步IO模型、轻量级HTTP支持和极致性能框架的应用,开发者可以实现从基础功能到工业级水准的飞跃,解决同步阻塞、资源管理等问题。

weixin_30509393的博客 782

代码重构的常用方法(C++实现)

代码重构的常用方法 (C++实现) 重构是在软件开发中改善已有代码的一种方法,通过代码重构可以改进软件的设计、使得软件更容易理解、有利于Bug的发现以及提高后续开发效率。Martin Fowler在《重构:改善既有代码的设计》一书中对“重构”提供了两种形式的定义: 重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。 重构(动词):使用一...

ruanrunxue的博客 1万+

Python重构C++代码实战指南

C++中类的相等性通常由operator==明确定义:public:private:而在Python中,默认==比较的是对象身份(除非重载__eq__):self.x = xself.y = yprint(v1 == v2) # True(经重载后)若忽略此重载,两个内容相同的对象仍会被判为不等,导致集合操作、字典键查找等行为异常。此外,还需实现__hash__以便用作字典键:使用方式:# data.append(4) # 抛出异常。

weixin_29301059的博客 969

VS2010中的MFC工程名修改【转】

修改前的知识准备: 1、解决方案相关文件 AAA.sln文件和AAA.suo文件为MFC自动生成的解决方案文件,它包含当前解决方案中的工程信息,存储解决方案的设置。 2、工程相关文件 AAA.vcxproj文件是MFC生成的工程文件,它包含当前工程的设置和工程所包含的文件等信息。AAA.vcxproj.filters文件存放工程的虚拟目录信息,也就是在解决方案浏览器中的目录

guomeiran的专栏 4015

软件测试工程师笔试题

这里写目录标题全套软件测试面试笔试题(附答案)二、选折三、添空四、简答(资料是搜集整理的,感谢前辈的解题)无1.区别阶段评审的与同行评审2.什么是软件测试3. 简述集成测试的过程4 怎么做好文档测试5 白盒测试有几种方法6 系统测试计划是否需要同行审批,为什么7Alpha 测试与 beta 的区别8 比较负载测试,容量测试和强度测试的区别。性能测试从使用方便的角度来说,9 测试结束的标准是什么?10 描述软件测试活动的生命周期?11 软件的缺陷等级应如何划分? 软件测试工程师笔试题(附答案) 全套软件测试面

m0_46446002的博客 2548

RK3576处理器RT-Thread与Linux混合部署实现工业EtherCAT实时控制

实时操作系统(RTOS)是工业自动化领域的核心技术,通过精确的任务调度和中断响应机制确保微秒级控制精度。RT-Thread作为开源实时系统,采用优先级抢占式调度算法,能够满足运动控制和总线通信的硬实时需求。在工业显控一体设备中,实时系统需要与富应用系统协同工作,Linux系统负责图形界面和网络通信等非实时任务。混合部署架构通过共享内存和核间通信技术实现数据交换,既保证了实时性能又扩展了应用生态。EtherCAT工业以太网协议作为高性能现场总线,采用主从站结构和分布式时钟同步,可实现多轴运动控制的精确同步。R

weixin_33738982的博客 2627

C++ 案例指导(1)C++程序设计概述

0.      语言概念 计算机语言 语言是一套具有语法、词法规则的系统。计算机语言是计算机可以识别的语言,用于描述解决问题的方法,供计算机阅读和执行。 指令系统 指令是计算机可以识别的命令。一台计算机硬件系统能够识别的所有指令的集合,称为它的指令系统。 机器语言 由计算机硬件系统可以识别的二进制指令组成的语言称为机器语言。 汇编语言 将机器指令映射为一些可以被人读懂的

小龙王2010 1824

【可不卸载重装】VS2019新建项目里没有CUDA选项

问题:VS2019 新建项目里没有CUDA选项,vs也属性没有cuda。这是因为你在Visual Studio 2019安装之前,先安装了CUDA Toolkit。

qq_62844901的博客 1318

c/c++开发,无可避免的代码重构实战(基于前文TCP/Socket通信开发案例)

c/c++开发,无可避免的代码重构实战(基于前文TCP/Socket通信开发案例)

技术需要分享 967

激光雷达+相机标定避坑指南:手把手教你用KITTI数据完成坐标转换

本文详细介绍了激光雷达与相机标定的实战指南,利用KITTI数据集完成精准坐标转换。从数据准备、工具链搭建到标定矩阵加载与验证,再到坐标转换流水线的工程实现,提供了全面的避坑技巧和工程实践建议。特别强调了多传感器融合中的常见问题及解决方案,帮助开发者提升自动驾驶和机器人感知系统的环境感知精度。

book8的博客 834

<视觉>——单目相机的标定(简单原理+Opencv实现)

单目相机的标定原理大致如下: 世界坐标到像素坐标的转换。 期间的参数有S尺度因子,内参矩阵K,旋转矩阵R,平移矩阵T,一共八个未知数。 在Opencv中我们可以方便的根据相机拍摄不同位姿的标定板图片来标定相机,从而求得这些参数,从而可以将相机下的坐标和实际的世界坐标联系起来,求取单应性矩阵H 参考: 单目相机综述:写的特别好 https://www.cnblo...

Kano365的博客 1万+

重构,第一个案例(C++版)——分解并重组Statement()

在上一篇文章——重构,第一个案例(C++版)——最初的程序,我们已经实现了一个影片出租程序的最初版本。我们也分析了,这个版本的程序虽然能跑起来,没有bug。但是,明显的,程序中有一些“代码的坏味道”。为了重构它,我们首先写出了一段测试代码,方便我们重构的时候进行测试。 下面我们的重点就在于将已经看得很不顺眼的长函数Statement进行大卸八块了。记住,代码块越小,代码的功能就越容易管理,代码的处...

吴尼玛cs 1273

ARINC429协议分析

如下表所示,ARINC429在发送一帧过程中,首先发送以START开始,以END结束,共发送32bits数据,其中0~7bits为Lable数据,9~8bits为SDI数据,10~28bits为数据,29~30bits为SSM,31bit为校验位End30292827262524校验位SSM2322212019181716DATA15141312111098SDI7654321Start。

5220

cpp-dependencies案例分析:如何使用依赖分析重构大型C++项目架构

想要重构大型C++项目却无从下手?依赖关系复杂到让你头疼?cpp-dependencies工具正是解决这一难题的终极利器!本文将为您详细介绍如何利用这个强大的依赖分析工具来优化和重构复杂的C++项目架构。 ## 🎯 什么是cpp-dependencies依赖分析工具? cpp-dependencies是一款专门用于分析C++源代码`#include`依赖关系的专业工具。它能扫描整个源代码树,

gitblog_01156的博客 205

电机瞬态分析基础(2):理想电机的基本假设和正方向规定

电机瞬态分析基础之理想电机假设条件和正方向规定

weixin_45670943的博客 1144

Coze实战:解锁多Bot协同与条件分支工作流的奥秘

本文详细介绍了在字节跳动Coze平台实现多Bot协同与条件分支工作流的方法。首先阐述了Coze平台的功能优势,包括丰富的插件、知识库和工作流功能。随后分步骤讲解了多Bot协同的实现:从准备工作、创建Bot到协同配置,并提供了电商客服的实例演示。在条件分支工作流部分,详细说明了条件节点的作用类型、条件设定方法以及具体配置操作,强调测试优化的重要性。最后通过电商平台综合案例,展示了多Bot协同与条件分支的实际应用效果,并针对常见问题提供解决方案。文章为开发者在Coze平台上构建智能应用提供了实用指导,有助于提升

远程部署调试 运行安装 项目调试 二次开发 项目技术新 持续迭代 部分源码免费分享 449

重构,第一个案例(C++版)——运用多态取代与价格相关的条件逻辑

经过上一篇文章——重构,第一个案例(C++版)——分解并重组Statement()中的重构,我们的程序已经有了很大的进步了。但,我们重构的步伐并不能停歇下来。因为,很快客户就开始嘀咕起新需求了。让我们看看这次客户想要些什么。 注意,客户要提新需求了! 客户想要修改影片的分类规则。但,他们自己也没想好怎么改。可能会有新的分类方法被引入,原来的分类方法也可能会有改动(各位同学,这种情况是不是很熟悉!)...

吴尼玛cs 654

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

【C++学习】谈谈我对C++代码重构的认识

C++系列文章 【C++学习】C++ 中delete一个指针后指针是什么状态?应该要注意什么? 目录C++系列文章前言一、C++相关基础1. 类和对象2. 继承3. 函数重载4. 多态5. 虚函数6. 抽象类二、重构的技巧与方法1. 单元测试2. 函数的重构3. 类的重构4. 数据的重构三、可能的实施:notion:总结 前言 C++对类的重构,首先要对C++的数据结构,代码结构,封装继承虚函数等的使用要有基础,其次要对已有代码有充分的了解和认知,最后是掌握一定的重构技巧和方法。 重构时需要有一定的

Astro的成长之路 1027

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

实践《重构:改善既有代码的设计(第2版)》示例(C++版)(一)

最近在拜读《重构:改善既有代码的设计》(第二版),与GoF的《设计模式》一同被誉为软件工程师的两本圣经。通过C++来改写书中案例,一方面为了自己更好地理解,另一方面为志同道合的C++爱好者提供一份参考

小小笑笑川的博客 1937

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

C++ 重构技巧

多年前,当 C++ 还不够成熟的时候,全球代码库很小,我们可以轻松地阅读、审核代码,然后将其用于构建项目,产生无限可能。或许这种情况可能从未出现,但我们可以想象一下,如果程序虽然简短简单,但很完整,不需要扩展功能。这种情况下,可能没必要进行代码重构。然而,如果需要检查或更新代码库,无论是扩展代码库,还是基于不同语言改进代码库,亦或是仅为了检查效率而审核代码库,重构代码都很有必要。...

讨论软件开发加速、持续集成/持续交付(CI/CD)、C++、开发者生产力以及所有相关话题。 1902

在C++中的进行重构

在C++中的进行重构   最近在修改一个项目,C++的一个服务器端的程序。它用来连接两种终端一种是发起终端一种是被控终端。以前只支持一个发起终端,现在需要将其改成能够支持多个发起终端的情况,当看了程序之后,首先就打算先整理一下以有的代码。或者也能说得上是一种重构吧。先说说这个程序:主要有以下的三个类CSrvCSrvDlgCSockCStartSock CEndSock 是CSock的两个继承类,也

3735

RuoYi-Cpp:C++ 高性能版若依管理系统

基于 Drogon + PostgreSQL 实现的 C++ 版若依框架,完全兼容 RuoYi-Vue 前端,启动内存仅数 MB、秒级启动,支持 Windows/Linux多平台,内置多种权限、认证、监控能力。

2302_80175019的博客 607

面向C++运算高效性的代码重构策略与案例分析

合并功能相似的函数,减少不必要的函数调用层级。在一个游戏AI逻辑中,原本复杂的条件判断用于决定AI行为,将不同行为判断逻辑提取为独立函数,通过函数指针或虚函数调用,简化了控制流,使代码更易读,也利于编译器优化,提升了AI运算效率。比如在一个计算员工薪资的函数中,每次循环都重新计算税率,而税率在整个计算过程中是固定值,这就造成了不必要的重复运算,浪费CPU资源,拖慢程序运行速度。在上述计算员工薪资的例子中,将固定税率提取为全局常量或局部常量,在循环外计算一次,避免循环内重复计算,减少运算量,提升效率。

yzz19900508的博客 383

老系统动不得?5个真实案例教你安全重构老旧C++工程

解决老旧C++系统重构难题,5个真实案例揭示安全升级路径。涵盖金融、通信等场景,基于2025全球C++及系统软件技术大会:老旧C++项目的标准化改造指南,分享渐进式重构、依赖解耦与自动化测试等关键方法,低风险实现代码现代化,值得收藏

FuncWander的博客 1095

探索C++调试新境界:IceCream-Cpp

在C++编程中,打印调试是不可或缺的一部分。然而,传统的调试方式往往繁琐而效率低下。现在,让我们一起发掘[IceCream-Cpp](https://github.com/renatoGarcia/icecream-cpp),一个专为C++11及其以上版本设计的轻量级单头文件库,它将让您的调试生活变得简单而高效。 ## 项目介绍 IceCream-Cpp的灵感来源于Python的IceCrea

gitblog_00065的博客 590

《重构》读书笔记(二)——第一章 第一个重构案例

第1章 重构,第一个案例          作者在第一章通过一个影片出租的例子,试图阐述重构的基本过程和步骤。看得出来,作者对这个案例给予厚望,花了很大的篇幅。正因如此,我没有理由不好好学习这一章。          影片出租的例子本身不难,但我足足花了一整个下午学习了这个例子。我先是老老实实的把代码用C++重抄了一遍,然后跟着作者的步伐,一步步重构,以期体验“重构改善设计”的完美过程。

e5Max的专栏 1847
上一篇: 找出那些代码里的坏味道吧——《重构》笔记(一)
下一篇: 重构,第一个案例(C++版)——分解并重组Statement()
拉普拉斯妖1228
博客等级 码龄18年 60粉丝 149原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值