'''6.21,6.28 重构lunch代码道场记实''' [[BR]] 代码道场的参与者:秦鸿源,陈阳,王安宁,丁健勇,邝巨恒,李炳, 李峰 [[BR]] 地点:4G会议室 [[BR]] 论坛小组在5.10,5.17进行了对命名为lunch的题目对进CodeKata练习,活动效果不错,小组成员普遍觉得受匪浅.[[BR]] 活动下来后,秦鸿源提议,我们其实可以把lunch做得更完美,由于大家平时的工作都比较忙碌,在日常开发中,为了尽快完成工作,对代码优化与重构的重视不够,[[BR]] 秦鸿源的提议很不错,可以让大家互相学习重构,提升大家对代码重构的能力,大家也都非常认同这个提议,于是我们决定用一次CodeKata活动来重构lunch.[[BR]] 由于,5.10号活动,被其它事情打断,在5.17日,我们进行了弥补.[[BR]] 这次重构的主要指导思想是:如何让代码变得更容易维护.[[BR]] 检验的指标是,如果我在lunch中新开一个打饭的窗口,难吗?[[BR]] 很明显,目前的代码,是困难的,代码中有太多的难以维护的变量,和魔术数字,新开一个打饭窗口,需要修改大量的代码。 [[BR]] 这次的重构活动,大家也是摸着石头过河,丁健勇,秦鸿源首先对有着相似的逻辑代码,进行了小规模的封装,邝巨桓提出用数组存储各种打饭的类型,这样可以用数组来遍历,避免硬代码的出现。[[BR]] 陈阳提出,用枚举来代替数组,有着更好的可维护性与可读性.我们新增了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; } } }}} 使用枚举,将原先设计的那些松散的常量,套餐价格,每种套餐的最大排队人数,每种套餐的第一志愿人数,打饭阿姨每分钟打饭数,聚合在一起。 [[BR]] 使得新开一个打饭窗口变得更容易,同时,枚举也支持遍历,让代码变得更灵活 [[BR]] 引入LunchType后我们大刀阔斧地对主代码Lunch.java进行了重构 [[BR]] {{{ 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 queue = new HashMap(); private LinkedList people = new LinkedList(); private Map personCounts = new HashMap(); public Map getQueue() { return queue; } public void setPersonCounts(Map personCounts) { this.personCounts = personCounts; } public Map getPersonCounts() { return personCounts; } public void setQueue(Map queue) { this.queue = queue; } public LinkedList 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()); } } }}} 可以看到代码比上一版,干净了许多. 这里有一个小插曲,在代码道场快要结束的时候,第一版中有一段处理打饭业务逻辑的代码,如下:[[BR]] {{{ ... 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(); ... }}} 这段代码是硬代码,不易维护,不易扩展,起初大家也认为这个地方,要让它"活"起来,有难度。[[BR]] 但大家一讨论,枚举可以遍历,values() 可以 得到数据, 而且枚举的ordinal(),方法可以知道当前枚举的位置。[[BR]] 可以尝试一下,由邝巨恒编码,陈阳,丁健勇,王安宁等同事,协助编码,活动达到高潮[[BR]] 最后,成功逆袭,拿下了它。 [[BR]] 当然,我们的重构是建立在测试驱动的基础之上的,单元测试代码如下。 [[BR]] {{{ /* * 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 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 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 queue = lunch.getQueue(); Map 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 queue = lunch.getQueue(); Map 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 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 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)); } } }}} 此次的重构,基本上满足了我们的活动目的,但也有一些不够完美的地方.邝巨桓提议说: 我们重构,好像就是在用枚举替代数组,并没有让大家学习到更多的知识。[[BR]] 由于时间的原因,我们这期的活动,只能做这么多. 如果时间充裕,我们可以再做一次大型的重构,比如说,我们的题目,是一个典型的生产者-消费者模式,[[BR]] 我们可以把程序设计成多线程模式,让职工进入食堂排队,和阿姨打饭同时进行.[[BR]] 再说个小福利,前几次代码道场活动,都是用的笔记本电脑的的键盘,大家都反应很难用,最后,我们借用了李炳岳的usb接口健盘。