我一辈子见过最糟糕的代码

我一辈子见过最糟糕的代码
翻译的很烂,具体请看原文: http://blog.cherouvim.com/the-worst-codebase-ive-seen-in-my-life/
最近,我接了一个庞大的老项目(我们称之为FailApp),其中包括很多不合格的代码。 我们将其分为三类。
 1) LoLs: 低级笑话代码(LOL是文字聊天里的大笑表情)
 2) WTFs: 他妈的烂代码(what the fuck)
 3)ShowStopper: 超级危险代码,致命的,制造P1级事故的代码!
直接举例:

LoL#1
在应用程序上下文中使用“Prod”这个的名称。
导致后面的这个测试环境http://123.12.34.123:7001/FailAppProd看起来像生产环境

LoL#2
EscapeIlegalWords eiw = new EscapeIlegalWords();
foo = eiw.escapeIlegalWords(foo);
public static String escapeIlegalWords(String code) {
    code = code.replaceAll("&lt;", "<");
    code = code.replaceAll("&gt;", ">");
    code = code.replaceAll("&amp;", "&");
    code = code.replaceAll("&apos;", "'");
    code = code.replaceAll("&quot;", "\"");
    code = code.replaceAll("&#13;", "");
    return code;
}
 - 我的妈呀,这是调用静态方法的新的实例吗?
 - HTML过滤,还是非法字符过滤?
- 过滤是单向的吗?
- 起这样的类名字有意义吗?
- 这样的算法,性能如何?

LoL#3
session.setAttribute("contentXML", null);
session.removeAttribute("contentXML");
session.setAttribute("contentOwnerList", null);
session.removeAttribute("contentOwnerList");
session.setAttribute("structuresVector", null);
session.removeAttribute("structuresVector");
session.setAttribute("selectedThematic", null);
session.removeAttribute("selectedThematic");
session.setAttribute("selectedTarget", null);
session.removeAttribute("selectedTarget");
session.setAttribute("targetList", null);
session.removeAttribute("targetList");
session.setAttribute("thematicList", null);
session.removeAttribute("thematicList");
session.setAttribute("metadatas", null);
session.removeAttribute("metadatas");
 –确定吗?请确保你真的想从seesion删除变量...删两次吗?
LoL#4
ArrayList arrActions = new ArrayList();
HashMap order = new HashMap();
public ArrayList getHighLights(int idStructure, int language, int home) { ...
 - 非常感谢你对类型的具体声明。 不过你听过接口和集合API的设计理念没?

LoL#5
ArrayList avlanguages=null;
if (request.getAttribute("avlanguages")!=null){
    avlanguages=(ArrayList)request.getAttribute("avlanguages");
}
<%if (avlanguages!=null && avlanguages.size()>0){%>
<%for(int x=0;x<avlanguages.size();x++) { %>
 -是的,这是太多的Java代码的JSP页面内,而不是使用JSTL <c:forEach...

 LoL#6
一些拼写错误,出现在日志和代码中:
  END GENERTAION THEMATICS.
 
  updateStatusDocumentDeplubishCron()

 LoL#7
  CommonDatosPopUp pop = new CommonDatosPopUp();
pop = (CommonDatosPopUp)popUp[i];

 - 这是伟大的!!! 感谢实现了之后,就把它扔掉。

 LoL#8
 ArrayList List = new ArrayList();
List = (ArrayList) baseManagerDao.getPosition(baseItemIdVar);
for (int i = 0; i < List.size(); i++)

- 再次,创建空ArrayList(),然后把它扔掉
- 变特殊命名List接口看起来像List (我不知道为什么这样的语言允许在这里污染自己)
- 看了这样的代码,你还有其他什么样的感想?
WTF#1
if (cm.getBlockLevels() != null) {
    request.setAttribute("blocklevel", cm.getBlockLevels());
}
if (cm.getUsers() != null) {
    request.setAttribute("users", cm.getUsers());
}
if (pm.getSigGroupPub() != null) {
    request.setAttribute("siggroups", pm.getSigGroupPub());
}
 - cm,pm(非常糟糕的局部变量名 – 这两个类是DAO)委托调用DAO的这些方法(很可能访问数据库)被调用两次
 - 为什么我们要在第一时间nullcheck? 如果结果是null,我们可以简单地把空(删除)压入request  attrs。 
 WTF#2
搜索“siggroup”。 在同一个controller(2000线),我找到了下面的代码:
 request.setAttribute(“siggroups”,pm.getSigGroupPub());
 request.setAttribute(“sigGroups”,sigGroups);
 request.setAttribute(“SigGroups”,hm.getSigGroups(Integer. ..
 - 一致性是程序员的生命之盐...

在模版之中:
List sig = (List) request.getAttribute("sigGroups");
if(sig == null) {
    sig = (List) request.getAttribute("SigGroups");
}
 - 如果你运气够好的话,你可以找到是哪一个对象
 WTF#3
没有javadoc,而且部分应用程序中最关键的是comment使非本土语言,包括非ASCII字符。 需要http://translate.google.com/帮助
 WTF#4
Classes with 2000 lines of code methods and 14 level nested ifs. These are usually do-everything controllers which serve many unrelated things from the same codebase (page results, binary downloads, csv). These are usually replicated 10 times with minor differences to accomodate slightly different use cases. Nough said
类文件包含2000行代码的方法和14个嵌套层级的if…else。经常是控制器做一切事情,这类控制器服务于许多来自同一个代码库不相关的事情(页面结果,二进制下载,cvs等)。还经常复制10次代码去适应略微不同的用例。
 WTF#5
pubHighlights = cm.getHighLights(structId, userLang,
                    Constants.PREDEFINED_SEARCH);
 – 大家正在利用变化的常量,而不是不同的方法或继承的方法:
public ArrayList getHighLights(int idStructure, String lang, int home) {
    ...
    if (home == 1) {
        listado = commonDao.getHightLightHome(idStructure);
    } else {
        listado = commonDao.getHightLightPredefinedSearch(idStructure);
    }
- facepalm for home==1 instead of Constant. good luck with debugging when that constant changes (home == 1, 为什么不使用常量?)

- by the way it turns out that the initial call should send Constants.HOME instead of Constants.PREDEFINED_SEARCH. It just happens that both equals 1.
顺便说下,应该用Constants.HOME来代替Constants.PREDEFINED_SEARCH常量,这两个常量只是凑巧都等于1.
 WTF#6
Absense of templating reuse. The 150+ JSP templates contain everything from html declarations to website footer (with copyrights and everything). Only with minor and insignificant differences due to inconsistent copy pasting of headers and whole pages with minor changes. Ever heard of include?
模板缺少复用。多于150个的jsp模板包含从html声明到站点页脚的一切东西。
 WTF#7
强大的表单验证, 看了下面的代码,你的感想是什么?:
if (appForm.getFileCV() == null || StringUtils.isEmpty(appForm.getFileCV().getFileName())){
    errors.add("fileCV", new ActionError("error.fileCV.required"));
}else if (!appForm.getFileCV().getFileName().toLowerCase().endsWith(".doc") && !appForm.getFileCV().getFileName().toLowerCase().endsWith(".pdf")
        && !appForm.getFileCV().getFileName().toLowerCase().endsWith(".rtf") && !appForm.getFileCV().getFileName().toLowerCase().endsWith(".sdc")
        && !appForm.getFileCV().getFileName().toLowerCase().endsWith(".zip") )
    errorExtension = true;
if (appForm.getFileLetter() == null || StringUtils.isEmpty(appForm.getFileLetter().getFileName())){
    errors.add("fileLetter", new ActionError("error.fileLetter.required"));
}else if (!appForm.getFileLetter().getFileName().toLowerCase().endsWith(".doc") && !appForm.getFileLetter().getFileName().toLowerCase().endsWith(".pdf")
        && !appForm.getFileLetter().getFileName().toLowerCase().endsWith(".rtf") && !appForm.getFileLetter().getFileName().toLowerCase().endsWith(".zip")
        && !appForm.getFileLetter().getFileName().toLowerCase().endsWith(".zip"))
    errorExtension = true;
/* if (fileForm == null || StringUtils.isEmpty(fileForm.getFileName())){
    errors.add("fileForm", new ActionError("error.fileForm.required"));
}else*/
if(appForm.getFileForm() != null && !StringUtils.isEmpty(appForm.getFileForm().getFileName()))
    if (!appForm.getFileForm().getFileName().toLowerCase().endsWith(".doc") && !appForm.getFileForm().getFileName().toLowerCase().endsWith(".pdf")
        && !appForm.getFileForm().getFileName().toLowerCase().endsWith(".rtf") && !appForm.getFileForm().getFileName().toLowerCase().endsWith(".sdc")
        && !appForm.getFileForm().getFileName().toLowerCase().endsWith(".zip"))
    errorExtension = true;
if(appForm.getFileOther1() != null && !StringUtils.isEmpty(appForm.getFileOther1().getFileName()))
    if (!appForm.getFileOther1().getFileName().toLowerCase().endsWith(".doc") && !appForm.getFileOther1().getFileName().toLowerCase().endsWith(".pdf")
            && !appForm.getFileOther1().getFileName().toLowerCase().endsWith(".rtf")&& !appForm.getFileOther1().getFileName().toLowerCase().endsWith(".sdc")
            && !appForm.getFileOther1().getFileName().toLowerCase().endsWith(".zip"))
        errorExtension = true;
if(appForm.getFileOther2() != null && !StringUtils.isEmpty(appForm.getFileOther2().getFileName()))
    if (!appForm.getFileOther2().getFileName().toLowerCase().endsWith(".doc") && !appForm.getFileOther2().getFileName().toLowerCase().endsWith(".pdf")
            && !appForm.getFileOther2().getFileName().toLowerCase().endsWith(".rtf")&& !appForm.getFileOther2().getFileName().toLowerCase().endsWith(".sdc")
            && !appForm.getFileOther2().getFileName().toLowerCase().endsWith(".zip"))
        errorExtension = true;
if(appForm.getFileOther3() != null && !StringUtils.isEmpty(appForm.getFileOther3().getFileName()))
    if (!appForm.getFileOther3().getFileName().toLowerCase().endsWith(".doc") && !appForm.getFileOther3().getFileName().toLowerCase().endsWith(".pdf")
            && !appForm.getFileOther3().getFileName().toLowerCase().endsWith(".rtf")&& !appForm.getFileOther3().getFileName().toLowerCase().endsWith(".sdc")
            && !appForm.getFileOther3().getFileName().toLowerCase().endsWith(".zip"))
        errorExtension = true;
 WTF#8
There are methods which return Vector (yes, 1999 called) and it turns out that the result is never being used but instead the purpose of the method is to mutate the parameters. Smart.
许多方法返回Vector并且结果证明它们已经不在被使用了。而这些方法存在的目的只是为了达到参数变异效果。聪明(讽刺^_^)。
 ShowStopper#1
cm.getHighLights is an expensive operation (calculates the nested menu of the website) and the following code is supposed to introduce caching into the game:
cm.getHighLights是一个代价昂贵的操作(生成网站树状菜单),下面的代码应该是引入缓存:
ArrayList pubHighlights = (ArrayList)request.getSession().getAttribute("publicationsHighlights");
if(pubHighlights == null || pubHighlights.size() == 0)
pubHighlights = cm.getHighLights(structId, userLang, Constants.PREDEFINED_SEARCH);
request.getSession().setAttribute("publicationsHighlights", pubHighlights);
 - it stores the result in the http session so there is a lot of waste of memory in case we have many users
把结果保存在http session中,在用户很多的场景下比较浪费内存
- the generated menu takes into account “structId” and “userLang”. The cache key is only one though (“publicationsHighlights” in the session), so if the user changes structId or userLang, the menu stays the same
生成的菜单需要考虑“structId” 和 “userLang”。而缓存的key只用到“publicationsHighlights”一个,因此当用户改变structId 或 userLang,菜单保持不变。
- changes on the menu structure are not reflected to already cached clients. They’ll see these changes only if they get a new session (come back later, use another browser etc)
当菜单结构变化时不会反映到已经缓存的客户端。只有他们获取新的会话才能看到改变的结果。
showstopper#2
Application “does things” to the database on view. Things == if stuff are not there it silently creates them, so for example if you visit the about page of the French site and there is no content there (either from CM error or data corruption) it simply creates an empty one and inserts it into the database. This is nice and useful especially when the application thinks that there isn’t any content there, so after a couple of days you’ll find thousand of empty “about” pages under the French site waiting to be fixed by you.应用程序在视图层做一些数据库操作。
showstopper#3
Total incompetence in exception design and handling. The usual anti-pattern of swallow and return new ArrayList() is followed through the system. Database errors are masked and the system goes on doing what it was doing (e.g continuing with other parts of data changes, email dispatching etc).
showstopper#4
“Change navigation element==edit property” anti-pattern. This is sick. Imagine a CRUD page with a couple of filters on top and an entities listing below. In the year filter you choose 2010 and hit GO. The listing is updated with entries from 2010. Now you change the year filter to 2011 but do not hit GO. Instead you hit EDIT on one of the 2010 entities below. What happens is that the 2011 value from the filter is transfered into the (hidden) element of the edit form. As soon as you hit SUBMIT the entity now belongs on 2011. Nice.
showstopper#5
The search is FUBAR. A single search for “foo” issues 200.000 db queries and requires 5 minutes on the production server because:
- it first does “select *” the whole publications database in sorted batches of 1000 back to a collection.
- it then feeds this collection into a method which filters things out.
- while filtering some entity methods are accessed and due to bad fetch plan from hibernate tons of N+1 statements are executed.
showstopper#6
“Toxic servlets hierarchy”. All actions extend something (a base class) which extends servlet. The base class provides a public static spring factory field which is initialized on boot of the servlet. Yes, the only reason of existence of this base class is to provide this field and the actions extend it in order to get access to this public static field. Great.
“有毒的servlet层次”。所有action继承一个基类,这个基类继承servlet。基类提供一个公开的静态的spring工厂变量,此变量在servlet启动的时候初始化。基类存在的唯一理由就是提供这个变量,而action继承基类就是为了访问变量。太棒了(讽刺^_^)。
showstopper#7
Log4j & hibernate initialization rediscovered! Both libraries are being configured in the following fashion:
- read the log4j.properties and hibernate.cfg.xml configuration files from a custom location using a ContextListener
- write contents into a new file in the server’s root folder
- load from there
- documentation states that if application cannot boot the application’s configuration files should be removed from the server’s root folder!

The end.

你可能感兴趣的:(spring,Hibernate,jsp,log4j,servlet)