| Version 2 (modified by chenyang, 13 years ago) (diff) |
|---|
6.21,6.28 重构lunch代码道场记实
代码道场的参与者:秦鸿源,陈阳,王安宁,丁健勇,邝巨恒,李炳, 李峰
地点:4G会议室
论坛小组在5.10,5.17进行了对命名为lunch的题目对进CodeKata练习,活动效果不错,小组成员普遍觉得受匪浅.
活动下来后,秦鸿源提议,我们其实可以把lunch做得更完美,由于大家平时的工作都比较忙碌,在日常开发中,为了尽快完成工作,对代码优化与重构的重视不够,
秦鸿源的提议很不错,可以让大家互相学习重构,提升大家对代码重构的能力,大家也都非常认同这个提议,于是我们决定用一次CodeKata活动来重构lunch.
由于,5.10号活动,被其它事情打断,在5.17日,我们进行了弥补.
这次重构的主要指导思想是:如何让代码变得更容易维护.
检验的指标是,如果我在lunch中新开一个打饭的窗口,难吗?
很明显,目前的代码,是困难的,代码中有太多的难以维护的变量,和魔术数字,新开一个打饭窗口,需要修改大量的代码。
这次的重构活动,大家也是摸着石头过河,丁健勇,秦鸿源首先对有着相似的逻辑代码,进行了小规模的封装,邝巨桓提出用数组存储各种打饭的类型,这样可以用数组来遍历,避免硬代码的出现。
陈阳提出,用枚举来代替数组,有着更好的可维护性与可读性.我们新增了LunchType.java这个枚举类,来封装打饭的类型。
package lunch;
/**
*
* @author pc
*/
public enum LunchType {
Type5(5, 100, 300, 5),
Type11(11, 50, 280, 3),
Type13(13, 30, 150, 2),
Type15(15, 15, 70, 1),
Type20(20, 10, 20, 1);
private int value;
private int waitNum;
private int totalPeople;
private int handleNum;
private LunchType(int value, int waitNum, int totalPeople, int handleNum){
this.value = value;
this.waitNum = waitNum;
this.totalPeople = totalPeople;
this.handleNum = handleNum;
}
public static int countPepple(){
int sum = 0;
for(LunchType lunchType : LunchType.values()){
sum += lunchType.getTotalPeople();
}
return sum;
}
public int getValue() {
return value;
}
public int getWaitNum() {
return waitNum;
}
public int getTotalPeople() {
return totalPeople;
}
public int getHandleNum() {
return handleNum;
}
}
使用枚举,将原先设计的那些松散的常量,套餐价格,每种套餐的最大排队人数,每种套餐的第一志愿人数,打饭阿姨每分钟打饭数,聚合在一起。
使得新开一个打饭窗口变得更容易,同时,枚举也支持遍历,让代码变得更灵活
引入LunchType后我们大刀阔斧地对主代码Lunch.java进行了重构
package lunch;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
/**
*
* @author pc
*/
public class Lunch {
private Map<LunchType, Integer> queue = new HashMap<LunchType, Integer>();
private LinkedList<LunchType> people = new LinkedList<LunchType>();
private Map<LunchType, Integer> personCounts = new HashMap<LunchType, Integer>();
public Map<LunchType, Integer> getQueue() {
return queue;
}
public void setPersonCounts(Map<LunchType, Integer> personCounts) {
this.personCounts = personCounts;
}
public Map<LunchType, Integer> getPersonCounts() {
return personCounts;
}
public void setQueue(Map<LunchType, Integer> queue) {
this.queue = queue;
}
public LinkedList<LunchType> getPeople() {
return people;
}
public int getPersonCountByPrice(LunchType type){
return personCounts.get(type);
}
/**
* 初始化进场顺序
*/
public void init() {
for(LunchType lunchType : LunchType.values()){
for (int i = 0; i < lunchType.getTotalPeople(); i++) {
people.add(lunchType);
}
queue.put(lunchType, 0);
personCounts.put(lunchType, 0);
}
Collections.shuffle(people);
}
public int getBalance() {
int balance = 0;
for(LunchType type : LunchType.values()){
balance += personCounts.get(type) * type.getValue();
}
return balance;
}
/**
* 每分钟时的处理方法
*/
public void checkpoint() {
out: for (int i = 0; i < 15; i++) {
if (!people.isEmpty()) {
LunchType p = people.poll();
for (LunchType type : LunchType.values()) {
if (type.ordinal() == 0) {
if (queueIsNotFull(type) && type == p) {
queue.put(type, queue.get(type) + 1);
personCounts.put(type, personCounts.get(type)+1);
continue out;
}
}
if (type.ordinal() > 0 && type.ordinal() < LunchType.values().length - 1) {
if (queueIsNotFull(type) && type.getValue() >= p.getValue()) {
queue.put(type, queue.get(type) + 1);
personCounts.put(type, personCounts.get(type)+1);
continue out;
}
}
if (type.ordinal() == LunchType.values().length - 1) {
if (queueIsNotFull(type)) {
queue.put(type, queue.get(type) + 1);
personCounts.put(type, personCounts.get(type)+1);
continue out;
}
}
}
}
}
handlePieceOfLunch();
}
public void handlePieceOfLunch() {
for(LunchType type : LunchType.values()){
handleQueue(type);
}
}
public boolean queueIsNotFull(LunchType lunchType){
return queue.get(
lunchType) < lunchType.getWaitNum();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
int times = LunchType.countPepple() % 10 == 0 ?
LunchType.countPepple() / 10 : LunchType.countPepple() / 10 + 1;
Lunch lunch = new Lunch();
lunch.init();
for (int i = 0; i < times; i++) {
lunch.checkpoint();
}
int total = lunch.getBalance();
System.out.println("total:" + total);
for(LunchType type : LunchType.values()){
System.out.println("getPersonCount"+type.getValue() +":" + lunch.getPersonCountByPrice(type));
}
}
private void handleQueue(LunchType type) {
queue.put(type, queue.get(type) - type.getHandleNum() < 0
? 0 : queue.get(type) - type.getHandleNum());
}
}
可以看到代码比上一版,干净了许多.
这里有一个小插曲,在代码道场快要结束的时候,第一版中有一段处理打饭业务逻辑的代码,如下:
...
for (int i = 0; i < 10; i++) {
String p = people.poll();
// 判断队列人数
boolean result = choose11(p);
if (result) {
queue.put("11", queue.get("11") + 1);
personCount11++;
} else {
result = choose13(p);
if (result) {
queue.put("13", queue.get("13") + 1);
personCount13++;
} else {
if (choose15(p)) {
queue.put("15", queue.get("15") + 1);
personCount15++;
}
}
}
}
handlePieceOfLunch();
...
这段代码是硬代码,不易维护,不易扩展,起初大家也认为这个地方,要让它"活"起来,有难度。
但大家一讨论,枚举可以遍历,values() 可以 得到数据, 而且枚举的ordinal(),方法可以知道当前枚举的位置。
可以尝试一下,由邝巨恒编码,陈阳,丁健勇,王安宁等同事,协助编码,活动达到高潮
最后,成功逆袭,拿下了它。
当然,我们的重构是建立在测试驱动的基础之上的,单元测试代码如下。
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package lunch;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author pc
*/
public class LunchTest {
private Lunch lunch = null;
private LinkedList<LunchType> list;
public LunchTest() {
}
@BeforeClass
public static void setUpClass() {
}
@AfterClass
public static void tearDownClass() {
}
@Before
public void setUp() {
lunch = new Lunch();
list = lunch.getPeople();
list.offer(LunchType.Type11);
list.offer(LunchType.Type13);
list.offer(LunchType.Type11);
list.offer(LunchType.Type11);
list.offer(LunchType.Type15);
list.offer(LunchType.Type15);
list.offer(LunchType.Type11);
list.offer(LunchType.Type13);
list.offer(LunchType.Type13);
list.offer(LunchType.Type13);
list.offer(LunchType.Type13);
list.offer(LunchType.Type13);
list.offer(LunchType.Type13);
list.offer(LunchType.Type13);
list.offer(LunchType.Type15);
list.offer(LunchType.Type15);
list.offer(LunchType.Type11);
list.offer(LunchType.Type13);
list.offer(LunchType.Type13);
list.offer(LunchType.Type13);
}
@After
public void tearDown() {
}
@Test
public void testInit() {
lunch = new Lunch();
lunch.init();
List<LunchType> people = lunch.getPeople();
Assert.assertEquals(LunchType.countPepple(), lunch.getPeople().size());
int count11 = 0;
int count13 = 0;
int count15 = 0;
for (LunchType s : people) {
switch(s){
case Type11:
count11++;
break;
case Type13:
count13++;
break;
case Type15:
count15++;
break;
}
}
//TODO 验证首选11元的人数
Assert.assertEquals(LunchType.Type11.getTotalPeople(), count11);
//TODO 验证首选13元的人数
Assert.assertEquals(LunchType.Type13.getTotalPeople(), count13);
//TODO 验证首选15元的人数
Assert.assertEquals(LunchType.Type15.getTotalPeople(), count15);
//TODO 验证随机
}
@Test
public void testCheckpoint() {
//lunch.init();
Map<LunchType, Integer> queue = lunch.getQueue();
Map<LunchType, Integer> personCount = lunch.getPersonCounts();
for(LunchType lunchType : LunchType.values()){
queue.put(lunchType, 0);
personCount.put(lunchType, 0);
}
lunch.checkpoint();
Assert.assertEquals(10, list.size());
Assert.assertEquals(1, (int) queue.get(LunchType.Type11));
Assert.assertEquals(2, (int) queue.get(LunchType.Type13));
Assert.assertEquals(1, (int) queue.get(LunchType.Type15));
Assert.assertEquals(4, lunch.getPersonCountByPrice(LunchType.Type11));
Assert.assertEquals(4, lunch.getPersonCountByPrice(LunchType.Type13));
Assert.assertEquals(2, lunch.getPersonCountByPrice(LunchType.Type15));
lunch.checkpoint();
Assert.assertEquals(0, list.size());
Assert.assertEquals(0, (int) queue.get(LunchType.Type11));
Assert.assertEquals(7, (int) queue.get(LunchType.Type13));
Assert.assertEquals(2, (int) queue.get(LunchType.Type15));
Assert.assertEquals(5, lunch.getPersonCountByPrice(LunchType.Type11));
Assert.assertEquals(11, lunch.getPersonCountByPrice(LunchType.Type13));
Assert.assertEquals(4, lunch.getPersonCountByPrice(LunchType.Type15));
}
@Test
public void testCheckPointHavePeople() {
Map<LunchType, Integer> queue = lunch.getQueue();
Map<LunchType, Integer> personCount = lunch.getPersonCounts();
for(LunchType type:LunchType.values()){
queue.put(type, 0);
personCount.put(type, 0);
}
queue.put(LunchType.Type11, 50);
queue.put(LunchType.Type13, 30);
queue.put(LunchType.Type15, 10);
queue.put(LunchType.Type20, 5);
lunch.setQueue(queue);
lunch.checkpoint();
Assert.assertEquals(10, list.size());
Assert.assertEquals(47, (int) queue.get(LunchType.Type11));
Assert.assertEquals(28, (int) queue.get(LunchType.Type13));
Assert.assertEquals(14, (int) queue.get(LunchType.Type15));
Assert.assertEquals(0, lunch.getPersonCountByPrice(LunchType.Type11));
Assert.assertEquals(0, lunch.getPersonCountByPrice(LunchType.Type13));
Assert.assertEquals(5, lunch.getPersonCountByPrice(LunchType.Type15));
}
@Test
public void testHandlePieceOfLunch(){
Map<LunchType, Integer> queue = lunch.getQueue();
for(LunchType type:LunchType.values()){
queue.put(type, 0);
}
queue.put(LunchType.Type11, 30);
queue.put(LunchType.Type13, 20);
queue.put(LunchType.Type15, 10);
lunch.setQueue(queue);
lunch.handlePieceOfLunch();
Assert.assertEquals(27, (int) queue.get(LunchType.Type11));
Assert.assertEquals(18, (int) queue.get(LunchType.Type13));
Assert.assertEquals(9, (int) queue.get(LunchType.Type15));
queue = lunch.getQueue();
queue.put(LunchType.Type11, 0);
queue.put(LunchType.Type13, 0);
queue.put(LunchType.Type15, 0);
lunch.setQueue(queue);
lunch.handlePieceOfLunch();
Assert.assertEquals(0, (int) queue.get(LunchType.Type11));
Assert.assertEquals(0, (int) queue.get(LunchType.Type13));
Assert.assertEquals(0, (int) queue.get(LunchType.Type15));
queue = lunch.getQueue();
queue.put(LunchType.Type11, 3);
queue.put(LunchType.Type13, 2);
queue.put(LunchType.Type15, 1);
lunch.setQueue(queue);
lunch.handlePieceOfLunch();
Assert.assertEquals(0, (int) queue.get(LunchType.Type11));
Assert.assertEquals(0, (int) queue.get(LunchType.Type13));
Assert.assertEquals(0, (int) queue.get(LunchType.Type15));
}
@Test
public void testQueueIsNotFull() {
Lunch lunch = new Lunch();
Map<LunchType, Integer> queue = new HashMap();
queue.put(LunchType.Type11, 30);
queue.put(LunchType.Type13, 20);
queue.put(LunchType.Type15, 10);
lunch.setQueue(queue);
Assert.assertTrue(lunch.queueIsNotFull(LunchType.Type13));
}
}
此次的重构,基本上满足了我们的活动目的,但也有一些不够完美的地方.邝巨桓提议说:
我们重构,好像就是在用枚举替代数组,并没有让大家学习到更多的知识。
由于时间的原因,我们这期的活动,只能做这么多.
如果时间充裕,我们可以再做一次大型的重构,比如说,我们的题目,是一个典型的生产者-消费者模式,
我们可以把程序设计成多线程模式,让职工进入食堂排队,和阿姨打饭同时进行.
再说个小福利,前几次代码道场活动,都是用的笔记本电脑的的键盘,大家都反应很难用,最后,我们借用了李炳岳的usb接口健盘。
![(please configure the [header_logo] section in trac.ini)](http://www1.pconline.com.cn/hr/2009/global/images/logo.gif)