一个C++程序重构的例子——糟糕的代码
2012-09-01 21:08
696 查看
由于工作中常用c++的原因,在看《Refactoring — Improving the Design of Exsiting Code》这本书时,将java的例子写成了c++程序,略做总结,以深理解。
重构的习惯与思想,跟语言无关。但c++是一个难掌控的语言,记下这些优化改进,希望上进的c++ programmer们,能实现日积跬步,终至千里的梦想。
一、待优化的程序
程序描述:
(1)我们将优化的是一个影片出租用的小程序,该程序会记录每个顾客的消费金额并打印出来。
(2)程序输入为:顾客租的影片及对应的租期;程序的处理为:根据顾客租用影片时间及影片类型,计算费用;输出:打印消费单。
(3)影片有三种类型:普通影片、儿童影片及新上映影片。
(4)另外,模仿时下潮流,程序还提供了积分制度,为常客计算积分,积分会根据影片是否为新上映影片而不同。
程序代码如下:
影片类是个纯数据类。
Rental类表示某个顾客租了一部影片。
Customer类用来表示一个顾客。GenerateReports()函数用来生成某个顾客租赁影片的详细报表。
这三个类的UML类图描述如下:
GenerateReports()函数的完整代码如下:
GenerateReports函数的交互过程如下:
二、代码的评价
Martin Fowler 对上述代码做了如下评价:
(1)上述代码不符合面向对象精神。
(2)对于一个独立的小程序,快速而简单的设计一个程序并没有错;但如果上述代码是复杂系统中的一小部分,这段代码令人堪忧。
(3)Customer类里那个长长的GenerateReports函数承担了太多职责,做了很多本该由其他类完成的工作。
(4)编译器不会在乎上述的丑陋代码,它能正常完成工作,但是,当我们打算修改系统的时候,就涉及到了人,人会在乎上述代码的丑陋,因为人会夹杂美学上的判断。
(5)差劲的系统是很难修改的,因为很难找到修改点,如果很难找到修改点,程序员就很可能犯错,引入bug。
另外,如果需求变化了,上述代码会怎样?
(1)在web时代,如果用户希望输出HTML报表呢?
你会发现,在打印HTML报表的函数中,你根本无法重用GenerateReports函数的任何行为。
你唯一可做的是,编写一个全新的GenerateHtmlReports函数,重复GenerateReports的行为,当然,这种改变好像也不怎么费力,也许你很快就完成了!
(2)如果计费标准发生了变化,譬如租用新影片的计费发生变化,该怎么办呢?
这个时候,你必须同时修改GenerateHtmlReports()和GenerateReports(),而且要保证对两个函数修改的一致性。
当后续需求变化还需要修改时,ctrl+c和ctrl+v的带来的问题就来了,假若你编写的是一个永不修改程序也罢,但如果程序的生命周期很长,就可能会维护,会面临修改,那么复制和粘贴就会造成潜在的威胁。
(3)很快,需求变更又来了,用户决定改变影片的分类规则,但是还没有决定如何修改。
不管分类规则如何变化,总会影响计费规则和积分规则,就必须对GenerateReports的函数做出修改。
随着规则越来越复杂,两个函数的一致性越来越难保证,不犯错的机会将会越来越少。
三、重构
也许,你倾向于尽量少的修改程序,因为这段代码还可以工作,它并没有挂掉,但是,虽然它没有挂掉,它伤了程序员的心。它使程序员的生活变得难过,因为你发现,当你需要满足客户的修改需求时,你越来越难完成客户的需求......
相信很多程序员都有这样的经历,这个时候,就是需要重构技术登场了!
重构的习惯与思想,跟语言无关。但c++是一个难掌控的语言,记下这些优化改进,希望上进的c++ programmer们,能实现日积跬步,终至千里的梦想。
一、待优化的程序
程序描述:
(1)我们将优化的是一个影片出租用的小程序,该程序会记录每个顾客的消费金额并打印出来。
(2)程序输入为:顾客租的影片及对应的租期;程序的处理为:根据顾客租用影片时间及影片类型,计算费用;输出:打印消费单。
(3)影片有三种类型:普通影片、儿童影片及新上映影片。
(4)另外,模仿时下潮流,程序还提供了积分制度,为常客计算积分,积分会根据影片是否为新上映影片而不同。
程序代码如下:
// 影片类 class Movie { public: enum MovieType { REGULAR = 0, CHILDREN, NEW_REALESE }; Movie(const string &title, int type) { m_title = title; m_type = type; } int GetType() { return m_type; } int SetType(int type) { m_type = type; } string GetTitle() { return m_title; } private: // 影片名字 string m_title; // 影片类型 int m_type; };
影片类是个纯数据类。
// 租赁类 class Rental { public: Rental(Movie *movie, int days_rented) { m_movie = movie; m_days_rented = days_rented; } int GetDaysRented() const { return m_days_rented; } Movie * GetMovie() const { return m_movie; } private: // 租赁的影片对象 Movie *m_movie; // 租赁的天数 int m_days_rented; };
Rental类表示某个顾客租了一部影片。
// 顾客类 class Customer { public: Customer(const string &name) { m_name = name; } ~Customer() { CleanUp(); } void AddRental(Rental *rental) { m_rentals.push_back(rental); } string GetName() const { return m_name; } string GenerateReports(); private: void CleanUp() { vector<Rental *>::iterator it = m_rentals.begin(); for (; it != m_rentals.end(); ++it) { delete (*it); } m_rentals.clear(); } template <typename T> string NumToString(T num) { stringstream s_stream; s_stream << num; return s_stream.str(); } private: // 顾客的名字 string m_name; // 顾客租赁的所有影片 vector<Rental *> m_rentals; };
Customer类用来表示一个顾客。GenerateReports()函数用来生成某个顾客租赁影片的详细报表。
这三个类的UML类图描述如下:
GenerateReports()函数的完整代码如下:
string Customer::GenerateReports() { // 租赁消费总额 double total_amount = 0; // 积分 int renter_points = 0; // 返回的字符串形式的报表 string result = "Retal Record for " + GetName() + "\n"; vector<Rental *>::const_iterator it = m_rentals.begin(); for (; it != m_rentals.end(); ++it) { Rental *a_rental = *it; double amount = 0; // 计算每个租赁的消费额 switch (a_rental->GetMovie()->GetType()) { case Movie::REGULAR: amount += 2; if (a_rental->GetDaysRented() > 2) { amount += (a_rental->GetDaysRented() - 2) * 1.5; } break; case Movie::NEW_REALESE: amount += a_rental->GetDaysRented() * 3; break; case Movie::CHILDREN: amount += 1.5; if (a_rental->GetDaysRented() > 3) { amount += (a_rental->GetDaysRented() - 3) * 1.5; } break; default: break; } // 添加积分 ++renter_points; // 对两天以上的新片给以奖励积分 if (a_rental->GetMovie()->GetType() == Movie::NEW_REALESE && a_rental->GetDaysRented() > 1) { ++renter_points; } // 将本次租赁信息添加到results中 result += "\t" + a_rental->GetMovie()->GetTitle() + "\t" + NumToString(amount) + "\n"; total_amount += amount; } // 添加总消费额及积分 result += "Amount is " + NumToString(total_amount) + "\n"; result += "You earned " + NumToString(renter_points) + " frequent renter points."; return result; }
GenerateReports函数的交互过程如下:
二、代码的评价
Martin Fowler 对上述代码做了如下评价:
(1)上述代码不符合面向对象精神。
(2)对于一个独立的小程序,快速而简单的设计一个程序并没有错;但如果上述代码是复杂系统中的一小部分,这段代码令人堪忧。
(3)Customer类里那个长长的GenerateReports函数承担了太多职责,做了很多本该由其他类完成的工作。
(4)编译器不会在乎上述的丑陋代码,它能正常完成工作,但是,当我们打算修改系统的时候,就涉及到了人,人会在乎上述代码的丑陋,因为人会夹杂美学上的判断。
(5)差劲的系统是很难修改的,因为很难找到修改点,如果很难找到修改点,程序员就很可能犯错,引入bug。
另外,如果需求变化了,上述代码会怎样?
(1)在web时代,如果用户希望输出HTML报表呢?
你会发现,在打印HTML报表的函数中,你根本无法重用GenerateReports函数的任何行为。
你唯一可做的是,编写一个全新的GenerateHtmlReports函数,重复GenerateReports的行为,当然,这种改变好像也不怎么费力,也许你很快就完成了!
(2)如果计费标准发生了变化,譬如租用新影片的计费发生变化,该怎么办呢?
这个时候,你必须同时修改GenerateHtmlReports()和GenerateReports(),而且要保证对两个函数修改的一致性。
当后续需求变化还需要修改时,ctrl+c和ctrl+v的带来的问题就来了,假若你编写的是一个永不修改程序也罢,但如果程序的生命周期很长,就可能会维护,会面临修改,那么复制和粘贴就会造成潜在的威胁。
(3)很快,需求变更又来了,用户决定改变影片的分类规则,但是还没有决定如何修改。
不管分类规则如何变化,总会影响计费规则和积分规则,就必须对GenerateReports的函数做出修改。
随着规则越来越复杂,两个函数的一致性越来越难保证,不犯错的机会将会越来越少。
三、重构
也许,你倾向于尽量少的修改程序,因为这段代码还可以工作,它并没有挂掉,但是,虽然它没有挂掉,它伤了程序员的心。它使程序员的生活变得难过,因为你发现,当你需要满足客户的修改需求时,你越来越难完成客户的需求......
相信很多程序员都有这样的经历,这个时候,就是需要重构技术登场了!
相关文章推荐
- 对于一个糟糕的设计来说,通过阅读C++代码你可以很容易地识别出它。
- 一个C++程序例子——指向函数的指针、含有可变形参的函数(备查)
- C/C++做的一个简单界面程序扩充,向指定窗口发送消息源代码
- 收了100元辛苦费,写了一个最简单的C#ASP.NET的3层架构例子代码,源码是通过代码生成器生成的【写程序的效率神奇的高】
- 快速会用c++异常捕获机制(一个程序几行代码)
- 重构代码的一个例子
- 工作中发现一个代码重构的例子
- 一个c/c++程序是怎么从代码到可执行文件的
- openoffice中一个将java代码转换为c++代码的例子
- 一个简单关于学生成绩管理的C++程序代码,但是对于拷贝析构函数目前还是不知其作用何在
- BP神经网络学习和c++例子程序代码
- 编写一个函数,实现把C/C++程序代码中的注释去掉,并把结果返回。
- Java8Map示例:一个略复杂的数据映射聚合例子及代码重构
- 一个简单的录音软件程序代码【C++】
- 总结【代码重构】:Backbone.js做一个点击链接,跳过加载图片后即可跳转到效果图片的例子
- 每日一个C++小程序(四)--DOG类
- 再来一个很蛋的C++程序
- vector向量练习小例子c++代码实例及运行结果
- 从 C/C++ 程序调用 Java 代码
- C++ 一个程序获取另一个程序Edit控件的内容