写点什么

敏捷小游戏的思考 - 上篇

作者:LigaAI
  • 2022 年 3 月 11 日
  • 本文字数:4556 字

    阅读完需:约 15 分钟

敏捷小游戏的思考-上篇

最近,我们团队为了进一步提高协作效率,组织了一次趣味分享活动,通过游戏的方式,希望帮助技术部门的同事们更好地理解和实践敏捷开发的方法。


在这次活动中,“哪种行动方式更敏捷”、“为什么采用这种方法”等关乎敏捷概念理论和实践方法的思考被伙伴们提出来并公开讨论。作为开发小组的成员,我获益颇多,特将此次活动中的思考总结下来分享给大家,共同成长~



游戏规则:


10 个人翻 30 张牌,每人要把这 30 张牌的每 1 张牌都翻 1 遍,计算第 1 个人翻完 30 张牌所耗费的时间和所有人翻完 30 张牌所耗费的时间~


假设每一个红色方块耗时 x,每一个橙色方块耗时 y,每一个黄色方块耗时 z (x, y, z > 0)


方案 1—第 n 个人必须等第 n-1 个人翻完所有的 30 张牌才能开始翻牌,第 1 个人不用等


参考上图的结构,我们先计算第 1~5 个人的翻完 30 张牌的时间,然后再乘 2 就是总用时(不存在影响时间的意外情况):T1= ((5 + 1) * 5 / 2 * x + (4 + 1) * 4 / 2 * y + 25 * 5 * z) * 2 =30x + 20y + 250z


方案 2—第 n 个人必须等第 n-1 个人翻完第 5 张牌才能开始翻牌,第 1 个人不用等;且第 n 个人必须等第 n-1 个人翻完第 m 张牌,才能翻第 m 张牌,第 1 个人不用等


参考上图结构,先计算 1 ~ 10 个人翻完前 5 张牌的时间,然后再计算第 10 个人翻完 6 ~ 25 张牌的时间就是总用时(不存在影响时间的意外情况):T2 = ((5 + 1) * 5 / 2 * x + (4 + 1) * 4 / 2 * y) * 2 + 25 * z= 30x + 20y + 25z


方案 3—第 n 个人必须等第 n-1 个人翻完第 m 张牌,才能翻第 m 张牌,第 1 个人不用等


参考上图结构,先计算 1 ~ 10 个人翻完第 1 张牌的时间,然后再计算第 10 个人翻完第 2 ~ 5 张牌的时间,最后再加上第 10 个人翻完第 6 ~ 30 张牌的时间就是总用时(不存在影响时间的意外情况):T3= 10x + 4y + 25z


最终结果:


下面我们用程序来验证结论的正确性。假设 x = y = z = 1s,那最终结果应该是:


T1 = 30 + 20 + 250 = 300s


T2 = 30 + 20 + 25 = 75s


T3 = 10 + 4 + 25 = 39s


实际结果:


public class AgileTest {
private static final Logger LOGGER = LoggerFactory.getLogger(AgileTest.class);
public static void main(String[] args) { long time1 = way1(); long time2 = way2(); long time3 = way3(); LOGGER.info("time1: {} ms", time1); LOGGER.info("time2: {} ms", time2); LOGGER.info("time3: {} ms", time3); }
/** * <p>方案1</p> * <p>第n个人必须等第n-1个人翻完所有的30张牌才能开始翻牌,第1个人不用等。</p> * @return 耗时 ms */ public static long way1() { final String way = "way1"; long startTime = System.currentTimeMillis(); // 1.10个人 for (int i = 1; i < 11; i++) { AtomicInteger atomicI = new AtomicInteger(i); // 2.30张牌,每个人翻完30张牌,下个人才能开始 for (int j = 1; j < 31; j++) { node(way, atomicI, j); } } long endTime = System.currentTimeMillis(); return endTime - startTime; }
/** * <p>方案2</p> * <p>第n个人必须等第n-1个人翻完第5张牌才能开始翻牌,第1个人不用等;且第n个人必须等第n-1个人翻完第m张牌,才能翻第m张牌,第1个人不用等。</p> * @return 耗时 ms */ public static long way2() { final String way = "way2"; long startTime = System.currentTimeMillis(); CountDownLatch latch = new CountDownLatch(10); Map<Integer, Integer> map = new ConcurrentHashMap<>(); // 1.10个人 for (int outUser = 1; outUser < 11; outUser++) { int outLastUser = outUser - 1; Integer lastOutUserNode = 0; // 2.每个人必须等前面那个人翻完5张牌才能开始翻牌,第1个人例外 while (outUser != 1 && ((lastOutUserNode = map.get(outLastUser)) == null || lastOutUserNode < 5)) { } AtomicInteger atomicI = new AtomicInteger(outUser); Thread thread = new Thread(() -> { try { int innerUser = atomicI.get(); int lastInnerUser = innerUser - 1; // 3.30张牌 for (int j = 1; j < 31; j++) { // 4.翻牌的速度不能超过前1个人,第1个人例外,第30张除外 Integer lastInnerUserNode = 0; while (innerUser != 1 && ((lastInnerUserNode = map.get(lastInnerUser)) == null || lastInnerUserNode < j)) { } node(way, atomicI, j); map.put(innerUser, j); } } catch (Exception e) { LOGGER.error("Current Thread: " + Thread.currentThread().getName() + "+, exception: ", e); } finally { latch.countDown(); } }, "thread-user-" + outUser); thread.start(); }
try { latch.await(); } catch (InterruptedException e) { LOGGER.error("wait thread exception: ", e); }
long endTime = System.currentTimeMillis(); return endTime - startTime; }
/** * <p>方案3</p> * <p>第n个人必须等第n-1个人翻完第m张牌,才能翻第m张牌,第1个人不用等。</p> * @return 耗时 ms */ public static long way3() { final String way = "way3"; long startTime = System.currentTimeMillis(); CountDownLatch latch = new CountDownLatch(10); Map<Integer, Integer> map = new ConcurrentHashMap<>(); // 1.10个人 for (int outUser = 1; outUser < 11; outUser++) { AtomicInteger atomicI = new AtomicInteger(outUser); Thread thread = new Thread(() -> { try { int innerUser = atomicI.get(); int lastInnerUser = innerUser - 1; // 2.30张牌 for (int j = 1; j < 31; j++) { // 3.翻牌的速度不能超过前1个人,第1个人例外 Integer lastInnerUserNode = 0; while (innerUser != 1 && ((lastInnerUserNode = map.get(lastInnerUser)) == null || lastInnerUserNode < j)) { } node(way, atomicI, j); map.put(innerUser, j); } } catch (Exception e) { LOGGER.error("Current Thread: " + Thread.currentThread().getName() + "+, exception: ", e); } finally { latch.countDown(); } }, "thread-user-" + outUser); thread.start(); }
try { latch.await(); } catch (InterruptedException e) { LOGGER.error("wait thread exception: ", e); }
long endTime = System.currentTimeMillis(); return endTime - startTime; }
/** * 执行节点 * @param atomicI 第i个人 * @param j 第j张牌 */ private static void node(String way, AtomicInteger atomicI, int j) { int i = atomicI.get(); long currentTime = System.currentTimeMillis() / 1000;// System.out.printf("[%s] %s i-j: %d-%d %d\n", Thread.currentThread().getName(), way, i, j, currentTime); try { if ((i >= 1 && i <= 5 && j >= 1 && j <= 5 && i + j <= 6) || (i >= 6 && i <= 10 && j >= 1 && j <= 5 && i + j <= 11)) { Thread.sleep(1000); } else if ((i >= 1 && i <= 5 && j >= 1 && j <= 5 && i + j > 6) || (i >= 6 && i <= 10 && j >= 1 && j <= 5 && i + j > 11)) { Thread.sleep(1000); } else if (j >= 6) { Thread.sleep(1000); } } catch (Exception e) { LOGGER.error("node sleep exception: ", e); } }}
复制代码


日志输出:


17:52:10.645 [main] INFO com.peng.java_study.practice.zhikan.AgileTest - time1: 302746 ms17:52:10.648 [main] INFO com.peng.java_study.practice.zhikan.AgileTest - time2: 75632 ms17:52:10.648 [main] INFO com.peng.java_study.practice.zhikan.AgileTest - time3: 39348 ms
复制代码


从日志中可以看出,实际结果与预期基本一致,多出来的几百毫秒是程序在运行过程中不可避免的消耗。由它们耗时可知:T3 < T2 < T1,所以在不考虑其他因素影响的前提下,方案 3 是最敏捷的!


深入思考:在任何条件下,方案 3 都适用吗?


答案是否定的,在某些特殊情况下,方案 3 反而更慢!



假设,第 n - 1 个人翻完 1 ~ 30 张牌后,一起交接给第 n 个人的准备时间和沟通时间为 a

那么最终耗时为:T1' = T1 + (10 - 1) * a =T1 + 9a


若第 n - 1 个人翻完第 1 ~ 5 张牌后,一起交接给第 n 个人时,需要准备时间和沟通时间为 b;第 n - 1 个人翻完第 6 ~ 30 张牌中的任意一张牌后,交接给第 n 个人时,需要的准备时间与沟通时间 c。那么最终耗时为:T2'= T2 + (10 - 1) * b + 25 * 9 * c =T2 + 9b + 225c


假设第 n - 1 个人翻完 1 ~ 30 张牌中的任意一张牌后,交接给第 n 个人时,需要的准备时间与沟通时间为 d。那么最终耗时为:T3' = T_3 + 30 * 9 * d = T3 + 270d


现在,我们来验算一遍:当「准备时间与沟通时间」与「单个任务的执行时间时」的大小满足一定的条件时,方案 3 是否有可能比方案 1 慢?


假设 T3' > T1',那么:


T3 + 270d > T1 + 9a 即 10x + 4y + 25z + 270d > 30x + 20y + 250z + 9a


再次假设 x = y = z = 1s,那么上式就等同于:


10 + 4 + 25 + 270d > 30 + 20 + 250 + 9a 即


d > (261 + 9a) / 270 或 a < (270d - 261) / 9


如果 d = 1s,那 a < 1s 就可以使方案 1 快于方案 3...


由此可以得出结论:


在一定条件下,方案 3 未必是最优的,且这种情况很有可能发生...


最终结论:在大多数情况下,方案 3 会比方案 1 更敏捷,但在上述两小节中的特殊情况下,方案 3 反而是最慢的。因此在真实的生活场景中,还是要“因地制宜”,不能一概而论!


LigaAI 新一代智能研发协作平台 专注灵感 回归价值 享受成果 更多讨论和分享请期待“敏捷小游戏的思考-下篇”~

发布于: 刚刚阅读数: 2
用户头像

LigaAI

关注

新一代智能研发管理平台 2021.02.23 加入

AI赋能工作场景,想要做最懂开发者的智能研发管理平台~

评论

发布
暂无评论
敏捷小游戏的思考-上篇_团队管理_LigaAI_InfoQ写作平台