规范化程序设计的实例(1)

提出问题

首先要感谢实验室的章老师提出一个有现实应用的背景与问题:

智能手机中,用户十分关心已经使用的流量, 希望可以查看已经使用的网络流量; 本程

序主要实现过去 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很多的话, 那么要统计一个小时

的话,需要进行的累加操作数量巨大,耗费的时间很多;

规范化程序设计的实例(1),布布扣,bubuko.com

时间: 2024-10-29 04:09:35

规范化程序设计的实例(1)的相关文章

《JAVA程序设计与实例》记录与归纳--类与对象

类与对象 概念贴士: 1. 类必须先定义了才能使用.类是创建对象的模板,创建对象也叫类的实例化. 2. 在Java中,使用new关键字来创建对象,一般有一下3个步骤: 1)声   明:声明一个对象,包括对象名称和对象类型. 2)实例化:使用关键字new创建一个对象. 3)初始化:使用new创建对象时,会调用构造方法初始化对象. 3. 在类实例化的过程中自动执行的方法叫做构造方法,它不需要手动调用.构造方法可以在类实例化的过程中完成一些初始化的工作.构造方法的名称必须与类的名称相同,并且没有返回值

《JAVA程序设计与实例》记录与归纳--继承与多态

继承与多态 概念贴士: 1. 继承,即是在已经存在的类的基础上再进行扩展,从而产生新的类.已经存在的类成为父类.超类和基类,而新产生的类成为子类或派生类. 2. Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类. 3. 继承分为单继承和多重继承.单继承是指一个子类最多只能有一个父类.多继承是一个子类可以有两个以上的父类.Java语言中的类只支持单继承,多继承是通过接口(interface)来间接实现的. 4

12 泛型程序设计

为什么要使用泛型程序 泛型程序设计, 意味着编写的代码可以被很多不同类型的对象所重用. 例如, 我们并不希望为聚集String和File对象分别设计不同的类, 实际上,也不需要这样做, 因为一个ArrayList类可以聚集任何类型的对象, 这是一个泛型程序设计的实例. 没有泛型以前, ArrayList维护的时一个object类型的数组, 这样当获取一个值时, 必须使用强制类型转换. (并非类型判断, 而是强制的类型转换) 简单的泛型类定义 public class Pair<T> { pub

【NLP】条件随机场知识扩展延伸

条件随机场知识扩展延伸 作者:白宁超 2016年8月3日19:47:55 [摘要]:条件随机场用于序列标注,数据分割等自然语言处理中,表现出很好的效果.在中文分词.中文人名识别和歧义消解等任务中都有应用.本文源于笔者做语句识别序列标注过程中,对条件随机场的了解,逐步研究基于自然语言处理方面的应用.成文主要源于自然语言处理.机器学习.统计学习方法和部分网上资料对CRF介绍的相关的相关,最后进行大量研究整理汇总成体系知识.文章布局如下:第一节介绍CRF相关的基础统计知识:第二节介绍基于自然语言角度的

微软开源 2019:“讨好”开发者,当开源圈的“万人迷”

又到了年末各种盘点出炉的时候,开源圈今年虽然没有"GitHub 被微软收购"."Red Hat 被 IBM 收购"如此重磅且出圈的新闻,但依然不失精彩.开源圈作为开发者密度最高的圈子,微软作为开发者群体中"***率"最高的商业公司,微软与开源之间的故事,天生自带流量属性. 本文就来和大家一起回顾微软在 2019 年与开源相关的重要事件 —— 主要是引发较多关注和广泛讨论的新闻. 为了更好地理解微软的开源 2019,下面我们将会按不同的领域而非时间

关于《Windows程序设计(第五版)》中一个实例程序的疑问

最近一直在看Charlse Petzold的<Windows程序设计>,作为一个新得不能再新的新手,只能先照着书的抄抄源码了,之前的例子一直都很正常,但昨天遇到一个很诡异的BUG. 先看实例源码吧: 1 /*----------------------------------------------------------------- 2 ENVIRON.C -- Environment List Box 3 (c) Charles Petzold,1998 4 Copy by XXXX,2

Linux 程序设计学习笔记----终端及串口编程及实例应用

转载请注明出处,http://blog.csdn.net/suool/article/details/38385355. 部分内容类源于网络. 终端属性详解及设置 属性 为了控制终端正常工作,终端的属性包括输入属性.输出属性.控制属性.本地属性.线路规程属性以及控制字符. 其在系统源代码的termios.h中定义(具体的说明文档http://pubs.opengroup.org/onlinepubs/7908799/xsh/termios.h.html),其结构体成员主要是 Thetermios

Linux 程序设计学习笔记----文件管理实例应用

一.使用ls -l 以排序方式输出目录信息 1.需求以及知识点覆盖 ls -l 命令根据后面的参数将列出某文件即目录下的基本信息. 如果没有具体的目录或者文件,则列出当前目录下所有的非隐藏文件的信息,包括文件类型,文件权限,硬链接个数,拥有者.拥有者所在组,文件大小,文件更新时间等. such as : 若没有指定的文件,则输出所有目录下的文件信息: 所以,本应用要实现的基本功能和需要的知识点有: ①参数检查.包括参数个数检查,如果有多个需要列出信息的文件及目录,则遍历所有的参数,另外需要读取当

《80X86汇编语言程序设计教程》十 实模式与保护模式的切换实例

1.  再次声明,需要纯DOS系统才能看到满意测试效果.内容是演示实模式与保护模式切换实例,实现功能是16进制显示从110000H开始的256个字节的值 2.  源代码如下: 1 ;功能:演示实模式与保护模式的切换,16进制显示从110000H开始的256个字节的值 2 ;16位偏移的段间直接转移指令的宏定义,这是一个JMP指令到所描述的地址 3 4 JUMP macro selector,offsetv 5 db 0eah ;操作码 6 dw offsetv ;16位偏移 7 dw selec