提出问题
首先要感谢实验室的章老师提出一个有现实应用的背景与问题:
智能手机中,用户十分关心已经使用的流量, 希望可以查看已经使用的网络流量; 本程
序主要实现过去 1 分钟以及过去 1 小时已经使用的网络流总量,并且希望整个设计具备一定
的可扩展性,比如可以很方便地扩展到统计过去一天的网络流总量。
另外,程序在使用时还需要其运行速度和内存的使用情况。
学习目标:
1. 如何撰写可读性强的代码;
2. 基于性能和存储空间的代码优化设计;
3. 基于设计灵活性的代码优化设计;
概要:
1. 要解决的问题是统计过去一分钟以及一小时的网络流量总和;
2. 我们忽略界面等非核心的要素,关注核心数据结构和实现;
3. 在设计过程中,以接口设计为主导,往往是先给出接口设计,然后再考虑具体实现,
当然实际的设计过程中,可能是同时进行;
4. 设计实现的过程包含三个版本,从简单版本开始,迭代改进,直到最终的版本;第
一个版本只关注功能的实现,不考虑其他因素;第二个版本在前者基础上,考虑性
能和内存空间的优化;最后一个版本进一步对内存空间使用进行优化,并且同时考
虑设计的灵活性;
定义类接口
<span style="font-size:14px;"> class MinuteHourCounter{ //add a count void Count(int num_bytes); //return the count over this minute int MinuteCount(); //return the count over this hour int HourCount(); } </span>
在实现这个类之前,让我们首先看一看这些名字和注释,看看有哪些地方有改进的地方。
MinuteHourCounter 这个类名很好,很专门、具体,并且容易读出来。
Count()是有问题的,因为有人会认为它的意思是“返回所有时间里的总的计数”。这个名字有点违反直觉。问题是count既是个名词又是个动词,所以既可以是“我想要得到你所见过的所有样本的计数”意思也可以是“我想要你对样本进行计数”的意思。
下面有几个名字可供替代count():
1、Increment()
是会误导人的,因为它意味着一个只会增加的值。
2、Observe()
还可以,但是感觉有点模糊。
3、Record()
既是动词又是名称,不好
4、Add()
它既可以是“以算术方法增加”的意思,也可以是“添加到一个数据列表的意思”,在该情况下,两种情况兼有,所有适合。
所以修改为void Add(int num_bytes)。
但是参数名num_bytes太有针对性了,可以采用更加通用的count,既简单、通用并且暗示“非负数”。
版本 1:
下面是主要的接口设计,其中的 Add()对新到来的流量事件进行记录,而 MinuteCount()和
HourCount()分别用于统计过去一分钟和一小时的网络流量和;
<span style="font-size:14px;"> class MinuteHourCounter{ //add a count; void Add(int count); //return the accumalated count over the last 60 seconds; int MinuteCount(); //return the accumalated count over the last 3600 seconds; int HourCount(); } //下面是上述接口的具体实现: class MinuteHourCount{ struct Event{ Event(int count, time_t time):count(count), time(time){} int count; time_t time; }; list<Event> events; public: void Add(int count){ events.push_back(Event(count, time()); } int MinuteCount(){ int count = 0; const time_t now_secs = time(); for(list<Event>::reverse_iterator i = events.rbegin(); i!=events.rend()&&i->time > now_secs-60; ++i){ count += i->count; } return count; } int HourCount(){ int count = 0; const time_t now_secs = time(); for(list<Event>::reverse_iterator i = events.rbegin(); i!=events.rend()&&i->time > now_secs-3600; ++i){ count += i->count; } return count; } }; </span>
简评:
上述的代码将流量产生作为一个个事件保存起来,MinuteCount()和 HourCount()则根据
时间戳将符合要求的事件进行累加,并且返回累加值。
暂时不考虑设计本身,光考虑代码的可读性而言,上述代码存在如下的问题:
1. int MinuteCount(); 这里的循环操作可读性比较差;
2. int MinuteCount() 和 int HourCount()两者之间的代码重复度太大;可以考虑合并抽取;
<span style="font-size:14px;"> //对于上述的问题1的改进: int MinuteCount(){ int count = 0; const time_t now_secs = time(); for(list<Event>::reverse_iterator i = events.rbegin(); i!=events.rend(); ++i){ if(i->time > now_secs - 60) //将判断条件独立出来,增加可读性; count += i->count; else break; } return count; }</span>
观察上述的代码,就可以发现很多的代码都是重复的,因此对于上述问题 2 的改进:
<span style="font-size:14px;">int CountSince(int cutoff){ int count = 0; const time_t now_secs = time(); for(list<Event>::reverse_iterator i = events.rbegin(); i!=events.rend(); ++i){ if(i->time > cutoff) count += i->count; else break; } return count; } int MinuteCount(){ return CountSince(time()-60); } int HourCount(){ return CountSince(time()-3600); } </span>
从设计的角度讲,上述代码的主要问题:
1.需要的存储量是无限;因为需要保存到现在的所有的event,所以内存量不可小觑;
2.另外,计算的时间也不固定,如果每秒钟到来的event很多的话, 那么要统计一个小时
的话,需要进行的累加操作数量巨大,耗费的时间很多;