14悖论_简单故事悖论

2023-10-13 18:30
文章标签 简单 14 故事 悖论

本文主要是介绍14悖论_简单故事悖论,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

14悖论

我最近一直很感兴趣地关注 Kent Beck(@kentbeck),David Heinemeier Hansson(@dhh)和Martin Fowler(@martinfowler)之间的#isTDDDead辩论。 我认为,可以以建设性的方式挑战通常被认为是理所当然的想法是特别有益的。 这样,您就可以确定他们是经得起审查还是跌落在脸上。

讨论从@dhh开始,就TDD和测试技术提出以下几点,我希望我是对的。 首先,TDD的严格定义包括以下内容:

  1. TTD用于驱动单元测试
  2. 你不能有合作者
  3. 您无法触摸数据库
  4. 您无法触摸文件系统
  5. 快速的单元测试,只需眨眼即可完成。

他继续说,因此您要通过使用模拟来驱动系统的体系结构,从而使体系结构遭受隔离和模拟所有事物的驱动器的破坏,而“红色,绿色,重构”周期的强制实施也是如此说明性的。 他还指出,很多人会误以为您对代码没有信心,并且无法通过测试交付增量功能,除非您经过这条精心准备的TDD铺装之路。

@Kent_Beck说,TDD不一定包含大量嘲笑,讨论仍在继续……

我在这里解释了一下; 但是,正是使用TDD的理解和经验上的差异让我开始思考。

TDD真的有问题吗,还是@dhh对其他开发人员对TDD的解释的经验? 我不想把单词放在@dhh的嘴里,但是似乎问题是TDD技术的教条式应用,即使它不适用也是如此。 我给人的印象是,在某些开发机构中,TDD的退化程度仅比Cargo Cult Programming少。

理查德·费曼 “货品崇拜编程”一词似乎源于我发现真正令人鼓舞的人,已故的理查德·费曼教授。 1974年加州理工学院毕业典礼上,他发表了一篇题为《 货物崇拜科学-关于科学,伪科学和学习如何不自欺欺人的言论》的论文。 后来这成为他的自传的一部分: 当然,你一定是在开玩笑Feynman先生 ,我恳求你读这本书。

Feynman在其中重点介绍了来自多个伪科学的实验,例如教育科学,心理学,超心理学和物理学,其中保持开放思维,质疑一切并寻找理论缺陷的科学方法已被信念,礼仪和信念所取代:愿意将他人的研究成果视为理所当然的代替。

费曼从1974年的论文中得出的结论将《货物崇拜科学》总结为:

“在南海,有一群人崇拜货物。 在战争中,他们看到飞机降落了很多优质的材料,他们希望现在也能发生同样的事情。 因此,他们已经安排模仿跑道之类的东西,沿着跑道的侧面放火,为一个人坐在一个木制的小屋里,头上有两个木块,像耳机,竹节像天线一样伸出–他是管制员–他们等待飞机降落。 他们做的一切正确。 形式是完美的。 它看起来完全像以前一样。 但这是行不通的。 没有飞机降落。 我之所以称其为货物崇拜科学,是因为它们遵循所有显而易见的科学调查规则和形式,但由于飞机无法降落,它们缺少了一些必不可少的东西。”

您可以将这种想法应用于编程,在其中您会发现团队和个人执行惯例化的程序并使用技术,而没有真正理解他们背后的理论,而是希望他们能够工作,并且因为他们是“正确的事情”。

在该系列的第二次演讲中,@ dhh举了一个例子,他称之为“测试引起的设计损坏” ,在此我感到很兴奋,因为我已经看过很多次了。 我对基本代码的唯一保留是,对我来说,它似乎并不是TDD产生的,这种说法似乎有点局限。 我想说这更多是“货物崇拜”编程的结果,这是因为在我遇到过此示例的情况下,并未使用TDD。

如果您看过要点 ,您可能知道我在说什么; 但是,该代码是用Ruby编写的,对此我几乎没有经验。 为了更详细地探讨这一点,我认为我将创建一个Spring MVC版本并从那里开始。

这里的场景是一个非常简单的故事:所有代码所做的就是从数据库中读取对象并将其放入模型中进行显示。 没有额外的处理,没有业务逻辑,也没有要执行的计算。 敏捷的故事将是这样的:

Title: View User Details
As an admin user
I want to click on a link
So that I can verify a user's details

在这个“适当的” N层示例中,我有一个User模型对象,一个控制器和服务层以及DAO以及它们的接口和测试。

而且,这有一个悖论:您着手编写编写可能最好的代码,以使用众所周知的,并且可能是最受欢迎的MVC'N'层模式来实现故事,而对于这样一个简单的场景,最终结果是完全过头了。 就像@jhh所说的那样,某些东西已损坏。

在我的示例代码中,我正在使用JdbcTemplate类从MySQL数据库检索用户的详细信息,但是任何数据库访问API都可以。

这是示例代码,演示了实现故事的常规“正确”方法。 准备进行很多滚动操作...

public class User { public static User NULL_USER = new User(-1, "Not Available", "", new Date()); private final long id; private final String name; private final String email; private final Date createDate; public User(long id, String name, String email, Date createDate) { this.id = id; this.name = name; this.email = email; this.createDate = createDate; } public long getId() { return id; } public String getName() { return name; } public String getEmail() { return email; } public Date getCreateDate() { return createDate; } 
}

@Controller 
public class UserController { @Autowired private UserService userService; @RequestMapping("/find1") public String findUser(@RequestParam("user") String name, Model model) { User user = userService.findUser(name); model.addAttribute("user", user); return "user"; } 
}

public interface UserService { public abstract User findUser(String name); }

@Service 
public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; /** * @see com.captaindebug.cargocult.ntier.UserService#findUser(java.lang.String) */ @Override public User findUser(String name) { return userDao.findUser(name); } 
}

public interface UserDao { public abstract User findUser(String name); }

@Repository 
public class UserDaoImpl implements UserDao { private static final String FIND_USER_BY_NAME = "SELECT id, name,email,createdDate FROM Users WHERE name=?"; @Autowired private JdbcTemplate jdbcTemplate; /** * @see com.captaindebug.cargocult.ntier.UserDao#findUser(java.lang.String) */ @Override public User findUser(String name) { User user; try { FindUserMapper rowMapper = new FindUserMapper(); user = jdbcTemplate.queryForObject(FIND_USER_BY_NAME, rowMapper, name); } catch (EmptyResultDataAccessException e) { user = User.NULL_USER; } return user; } 
}

如果您看一下这段代码,反而看起来不错。 实际上,它看起来像是有关如何编写“ N”层MVC应用程序的经典教科书示例。 控制器将负责整理业务规则的责任传递给服务层,并且服务层使用数据访问对象从数据库中检索数据,该对象又使用RowMapper<>帮助程序类来检索User对象。 当控制器具有User对象时,它将其注入模型中以供显示。 这种模式是清晰可扩展的。 我们通过使用接口将数据库与服务隔离开来,并将服务与控制器隔离开来,并且我们正在使用带有Mockito的JUnit和集成测试来测试所有内容。 这应该是教科书MVC编码中的硬道理吗? 让我们看一下代码。

首先,有不必要的接口使用。 有人会说切换数据库实现很容易,但是谁会这样做呢? 1加,现代嘲讽工具可以使用类定义是这样,除非您的设计明确要求同一个接口的多个实现创建自己的代理,然后使用接口是没有意义的。

接下来,有一个UserServiceImpl ,它是惰性类反模式的经典示例,因为它除了无意义地委托给数据访问对象外,不执行任何操作。 同样,控制器也很懒惰,因为它在将结果User类添加到模型之前将其委托给lazy UserServiceImpl :实际上,所有这些类都是lazy类反模式的示例。

编写了一些惰性类之后,现在就可以对它们进行不必要的测试,包括非事件UserServiceImpl类。 只需要为实际上执行某些逻辑的类编写测试。

public class UserControllerTest { private static final String NAME = "Woody Allen"; private UserController instance; @Mock private Model model; @Mock private UserService userService; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); instance = new UserController(); ReflectionTestUtils.setField(instance, "userService", userService); } @Test public void testFindUser_valid_user() { User expected = new User(0L, NAME, "aaa@bbb.com", new Date()); when(userService.findUser(NAME)).thenReturn(expected); String result = instance.findUser(NAME, model); assertEquals("user", result); verify(model).addAttribute("user", expected); } @Test public void testFindUser_null_user() { when(userService.findUser(null)).thenReturn(User.NULL_USER); String result = instance.findUser(null, model); assertEquals("user", result); verify(model).addAttribute("user", User.NULL_USER); } @Test public void testFindUser_empty_user() { when(userService.findUser("")).thenReturn(User.NULL_USER); String result = instance.findUser("", model); assertEquals("user", result); verify(model).addAttribute("user", User.NULL_USER); } }

public class UserServiceTest { private static final String NAME = "Annie Hall"; private UserService instance; @Mock private UserDao userDao; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); instance = new UserServiceImpl(); ReflectionTestUtils.setField(instance, "userDao", userDao); } @Test public void testFindUser_valid_user() { User expected = new User(0L, NAME, "aaa@bbb.com", new Date()); when(userDao.findUser(NAME)).thenReturn(expected); User result = instance.findUser(NAME); assertEquals(expected, result); } @Test public void testFindUser_null_user() { when(userDao.findUser(null)).thenReturn(User.NULL_USER); User result = instance.findUser(null); assertEquals(User.NULL_USER, result); } @Test public void testFindUser_empty_user() { when(userDao.findUser("")).thenReturn(User.NULL_USER); User result = instance.findUser(""); assertEquals(User.NULL_USER, result); } 
}

public class UserDaoTest { private static final String NAME = "Woody Allen"; private UserDao instance; @Mock private JdbcTemplate jdbcTemplate; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); instance = new UserDaoImpl(); ReflectionTestUtils.setField(instance, "jdbcTemplate", jdbcTemplate); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testFindUser_valid_user() { User expected = new User(0L, NAME, "aaa@bbb.com", new Date()); when(jdbcTemplate.queryForObject(anyString(), (RowMapper) anyObject(), eq(NAME))).thenReturn(expected); User result = instance.findUser(NAME); assertEquals(expected, result); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testFindUser_null_user() { when(jdbcTemplate.queryForObject(anyString(), (RowMapper) anyObject(), isNull())).thenReturn(User.NULL_USER); User result = instance.findUser(null); assertEquals(User.NULL_USER, result); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testFindUser_empty_user() { when(jdbcTemplate.queryForObject(anyString(), (RowMapper) anyObject(), eq(""))).thenReturn(User.NULL_USER); User result = instance.findUser(""); assertEquals(User.NULL_USER, result); } }

@RunWith(SpringJUnit4ClassRunner.class) 
@WebAppConfiguration 
@ContextConfiguration({ "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml", "file:src/test/resources/test-datasource.xml" }) 
public class UserControllerIntTest { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; /** * @throws java.lang.Exception */ @Before public void setUp() throws Exception { mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); } @Test public void testFindUser_happy_flow() throws Exception { ResultActions resultActions = mockMvc.perform(get("/find1").accept(MediaType.ALL).param("user", "Tom")); resultActions.andExpect(status().isOk()); resultActions.andExpect(view().name("user")); resultActions.andExpect(model().attributeExists("user")); resultActions.andDo(print()); MvcResult result = resultActions.andReturn(); ModelAndView modelAndView = result.getModelAndView(); Map<String, Object> model = modelAndView.getModel(); User user = (User) model.get("user"); assertEquals("Tom", user.getName()); assertEquals("tom@gmail.com", user.getEmail()); } }

在编写此示例代码时,我将所有可以想到的内容添加到了组合中。 你可能会认为这个例子是“洁癖”在其建设特别是与包括冗余接口,但我已经看到这样的代码。

这种模式的好处在于它遵循了大多数开发人员所理解的独特设计。 干净且可扩展。 缺点是有很多类。 更多的类需要花费更多的时间来编写,并且您必须维护或增强此代码,因此更难掌握。

那么,有什么解决方案? 这很难回答。 在#IsTTDDead辩论中,@ dhh提供的解决方案是将所有代码放在一个类中,将数据访问与模型填充混合在一起。 如果您针对我们的用户案例实施此解决方案,您仍然会获得一个User类,但是所需的类数量将大大减少。

@Controller 
public class UserAccessor { private static final String FIND_USER_BY_NAME = "SELECT id, name,email,createdDate FROM Users WHERE name=?"; @Autowired private JdbcTemplate jdbcTemplate; @RequestMapping("/find2") public String findUser2(@RequestParam("user") String name, Model model) { User user; try { FindUserMapper rowMapper = new FindUserMapper(); user = jdbcTemplate.queryForObject(FIND_USER_BY_NAME, rowMapper, name); } catch (EmptyResultDataAccessException e) { user = User.NULL_USER; } model.addAttribute("user", user); return "user"; } private class FindUserMapper implements RowMapper<User>, Serializable { private static final long serialVersionUID = 1L; @Override public User mapRow(ResultSet rs, int rowNum) throws SQLException { User user = new User(rs.getLong("id"), // rs.getString("name"), // rs.getString("email"), // rs.getDate("createdDate")); return user; } } 
}

@RunWith(SpringJUnit4ClassRunner.class) 
@WebAppConfiguration 
@ContextConfiguration({ "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml", "file:src/test/resources/test-datasource.xml" }) 
public class UserAccessorIntTest { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; /** * @throws java.lang.Exception */ @Before public void setUp() throws Exception { mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); } @Test public void testFindUser_happy_flow() throws Exception { ResultActions resultActions = mockMvc.perform(get("/find2").accept(MediaType.ALL).param("user", "Tom")); resultActions.andExpect(status().isOk()); resultActions.andExpect(view().name("user")); resultActions.andExpect(model().attributeExists("user")); resultActions.andDo(print()); MvcResult result = resultActions.andReturn(); ModelAndView modelAndView = result.getModelAndView(); Map<String, Object> model = modelAndView.getModel(); User user = (User) model.get("user"); assertEquals("Tom", user.getName()); assertEquals("tom@gmail.com", user.getEmail()); } @Test public void testFindUser_empty_user() throws Exception { ResultActions resultActions = mockMvc.perform(get("/find2").accept(MediaType.ALL).param("user", "")); resultActions.andExpect(status().isOk()); resultActions.andExpect(view().name("user")); resultActions.andExpect(model().attributeExists("user")); resultActions.andExpect(model().attribute("user", User.NULL_USER)); resultActions.andDo(print()); } 
}

上面的解决方案将第一类的数量减少为两个:实现类和测试类。 所有测试方案都是在很少的端到端集成测试中满足的。 这些测试将访问数据库,但是在这种情况下会很糟糕吗? 如果每次到数据库的行程大约花费20毫秒或更短的时间,那么它们仍然会在几分之一秒内完成; 那应该足够快。

在增强或维护该代码方面,一个单一的小类比几个甚至更小的类更容易学习。 如果确实必须添加一堆业务规则或其他复杂性,那么将此代码更改为“ N”层模式将不会很困难; 但是问题是,如果/当需要进行更改时,可能会给经验不足的开发人员,该开发人员没有足够的信心进行必要的重构。 结果是,而且您肯定已经看过很多次了,新的更改可能会在这种一类解决方案的基础上出现问题,从而导致混乱的意大利面条式代码。

在实施这样的解决方案时,您可能不会很受欢迎,因为代码是非常规的。 这就是我认为这种单一类解决方案引起很多人争议的原因之一。 正是在每种情况下都严格应用的标准“正确方式”和“错误方式”编写代码的想法导致了这种完美的设计成为问题。

我想这全是课程的事 。 为正确的情况选择正确的设计。 如果我要执行一个复杂的故事,那么我会毫不犹豫地分担各种职责,但是在简单的情况下,这是不值得的。 因此,在结束之前,我想问问是否有人对上面显示的“ 简单故事悖论”有更好的解决方案,请告诉我。


1在过去的十年编程中,我曾经从事过一个项目,其中基础数据库已更改以满足客户需求。 那是很多年,数千里之外,并且代码是用C ++和Visual Basic编写的。

  • 此博客的代码可在Github上找到,网址为https://github.com/roghughe/captaindebug/tree/master/cargo-cult

翻译自: https://www.javacodegeeks.com/2014/06/the-simple-story-paradox.html

14悖论

这篇关于14悖论_简单故事悖论的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/205077

相关文章

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h

uva 10387 Billiard(简单几何)

题意是一个球从矩形的中点出发,告诉你小球与矩形两条边的碰撞次数与小球回到原点的时间,求小球出发时的角度和小球的速度。 简单的几何问题,小球每与竖边碰撞一次,向右扩展一个相同的矩形;每与横边碰撞一次,向上扩展一个相同的矩形。 可以发现,扩展矩形的路径和在当前矩形中的每一段路径相同,当小球回到出发点时,一条直线的路径刚好经过最后一个扩展矩形的中心点。 最后扩展的路径和横边竖边恰好组成一个直

业务中14个需要进行A/B测试的时刻[信息图]

在本指南中,我们将全面了解有关 A/B测试 的所有内容。 我们将介绍不同类型的A/B测试,如何有效地规划和启动测试,如何评估测试是否成功,您应该关注哪些指标,多年来我们发现的常见错误等等。 什么是A/B测试? A/B测试(有时称为“分割测试”)是一种实验类型,其中您创建两种或多种内容变体——如登录页面、电子邮件或广告——并将它们显示给不同的受众群体,以查看哪一种效果最好。 本质上,A/B测

poj 1113 凸包+简单几何计算

题意: 给N个平面上的点,现在要在离点外L米处建城墙,使得城墙把所有点都包含进去且城墙的长度最短。 解析: 韬哥出的某次训练赛上A出的第一道计算几何,算是大水题吧。 用convexhull算法把凸包求出来,然后加加减减就A了。 计算见下图: 好久没玩画图了啊好开心。 代码: #include <iostream>#include <cstdio>#inclu

uva 10130 简单背包

题意: 背包和 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <queue>#include <map>

JAVA用最简单的方法来构建一个高可用的服务端,提升系统可用性

一、什么是提升系统的高可用性 JAVA服务端,顾名思义就是23体验网为用户提供服务的。停工时间,就是不能向用户提供服务的时间。高可用,就是系统具有高度可用性,尽量减少停工时间。如何用最简单的方法来搭建一个高效率可用的服务端JAVA呢? 停工的原因一般有: 服务器故障。例如服务器宕机,服务器网络出现问题,机房或者机架出现问题等;访问量急剧上升,导致服务器压力过大导致访问量急剧上升的原因;时间和

简单的角色响应鼠标而移动

actor类 //处理移动距离,核心是找到角色坐标在世界坐标的向量的投影(x,y,z),然后在世界坐标中合成,此CC是在地面行走,所以Y轴投影始终置为0; using UnityEngine; using System.Collections; public class actor : MonoBehaviour { public float speed=0.1f; CharacterCo

docker-compose安装和简单使用

本文介绍docker-compose的安装和使用 新版docker已经默认安装了docker-compose 可以使用docker-compose -v 查看docker-compose版本 如果没有的话可以使用以下命令直接安装 sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-c