解惑

解己之惑,解人之惑

分类:Java (第10页共20页)

Java技术

软件开发的最大困难其实在于消除重复代码

这个并不是我一时兴起的胡言乱语,而是我的肺腑之言。如果你仔细的审视一下你所在的项目的代码,你可能会发现其中60%以上的代码是何其的类似,充斥其中的都是类似的判断和循环,在项目的框架或者架构稳定下来以后,项目的工作就是每天重复类似的工作,然后就是这些类似的代码的比例越来越大。如果项目的架构或者框架代码不够好,而某一天有一个需求导致这些代码都要进行某种简单或者复杂的更新,那么你的噩梦就来了。
软件开发的最大困难也就随之出现,如果你的框架足够好,共通的需求只会导致一个或者有限的几个地方的修改,如果框架不好,那么你就准备把那个修改重复几十遍或者几百上千遍吧。衡量一个软件的设计是否足够好,我想最重要的标准就是它能多大程度上消除这样的情况。仅仅有了好的架构其实也不够,开发人员可能会复制一段类似的代码,导致超出框架设计的代码重复,这个就是检验项目管理是否足够好了。

设计一个简单易懂、易于扩展、易于维护的框架并不是很难的,结合OO设计的原则和一些设计模式就可以做到,但是问题并没有结束,如何消除业务代码中存在的类似代码结构才是真正的问题所在。
我没有解决方案

当然,软件开发也并不是只有消除重复代码这一个问题,但是我感觉,如果能够较好的消除代码重复,那么软件的开发效率和质量以及可维护性将更好。
PS:我并没有说完全消除重复代码,代码的重复性是不可避免的,典型的例子就是单元测试代码。

如何设计API?

每个人设计API的思路可能都不一样,而我们公司的一些代码的API的设计很有意思,一个例子:
    public AppGroupConfigValueObj get(UserSession _userSession, AppGroupConfigValueObj invo) throws DataException
    {
        long start = System.currentTimeMillis();
        try
        {
            Long id = (Long) invo.getId();
            if (isDebug() )
                SessionLogManager.getInstance().getLogger(this.getClass()).debug("get group" + id);
            AppGroupConfig eb = this.appGroupHome().findByPrimaryKey(id);
            if (isDebug() )
                SessionLogManager.getInstance().getLogger( this.getClass() ).debug("Get( UserSession _userSession, AppGroupConfigValueObj invo) elapsed time = "+ (System.currentTimeMillis()-start));
            return get(eb, invo);
        }
        catch (Exception e)
        {
            throwException(e);
        }
        return null;
    }

    private AppGroupConfigValueObj get(AppGroupConfig eb, AppGroupConfigValueObj invo)
            throws Exception
    {
        AppGroupConfigValueObj vo = (AppGroupConfigValueObj) ServerUtil.getValueObj(eb);

        if (invo.getApps() != null)
        {
            Collection collection = eb.getApplications();
            if (invo.getApps().getCount() == 0)
                vo.setApps(new ValueObjList(collection.size()));
            else
            {
                Iterator it = collection.iterator();
                while (it.hasNext())
                {
                    AppConfig a = (AppConfig) it.next();
                    vo.addApp((AppConfigValueObj) ServerUtil.getValueObj(a));
                }
            }
        }
        return vo;
    }

解释一下,这个API的功能是这样的,主要参数AppGroupConfigValueObj 是一个Map结构的对象,另外包含一些父子关系,如果你需要的是一个轻量级的结果,那么传入的对象只需要设置ID就可以,典型的用法是这样的:
get(cuSession, new AppGroupConfigValueObj(appGroupId))
如果你需要一个重量级的结果,也就是需要它的一些子表的数据,那么就要设置子表的关系,并添加一个空的对象:
                    AppGroupConfigValueObj appVo = new AppGroupConfigValueObj(appGroupId);
                    appVo.addApp(new AppConfigValueObj());
                    get(cuSession,appVo);
还有第三种用法,就是你希望那些子表数据不填充,但是需要知道子表数据有几个,那么你的调用代码就是:
                    AppGroupConfigValueObj appVo = new AppGroupConfigValueObj(appGroupId);
                    appVo.setApps(new ValueObjList());
                    get(cuSession,appVo);

我不知道这种做法源自何处,但是很明显是原来的某位高人,而比我资历更老的一些同事沿用了这个做法并且没有在文档中记录这种方法,当我苦苦寻找一个简单的get(cuSession, appGroupId)方法时没有任何结果,最后看其它部分的代码才知道了这个诀窍。

如何替换\

以前也经常遇到这个问题,但是没有找到解决方案,今天再次遇到这个问题,搜索了下没有什么头绪,很多答案试验了下都是错误的,不得已,自己试吧,最终的结果是:
replaceAll("\\\\", "\\\\\\\\")
就是把字符串中的\替换成两个\,呵呵,变态吧。

没有命名规范的代码是开发人员的地狱

我们已经开始了一个新的阶段,这个阶段的开始并不顺利,因为我遇到了一个原来并不是很常见的问题,我竟然找不到合适的API供我使用,而且是我们系统的基础服务的API,原因不是别的,就是因为命名规范,第一是方法名,原来的很多API的方法名就是开发人员随意取的(我们没有编码规范或者说命名规范),一个公共的Session Bean的Proxy的方法名竟然叫getAll,而它仅仅是返回一个特定的对象的集合,一般这样的方法都应该叫getAllXxxx,第二是有些术语修改了,但是只是把界面上用户可以看到的文字修改了,后台的API的方法名和类名都没有修改,如果你不是相关的开发人员,你怎么可能弄清楚呢?而原来为什么没有修改方法呢?很简单,一些少数的地方使用反射调用,所以用IDE的重构方法可能会漏掉一些地方,所以他们不敢改,举一个真实的例子说明这个命名的修改导致了多么大的问题:
app group-> app
app template -> component
app -> component instance
说实话,在这一点上,我们的产品实在是失败,因为自从我做开发依赖,所去过的公司还没有说哪个公司没有命名规范的。

JUnit的Failure Trace的一个问题

不知道有没有人注意过或者写过,就是如果你在使用assertEquals比较一个字符串和一个对象的时候,Failure Trace给出的信息可能会误导你:
junit.framework.AssertionFailedError: expected:<<script language="javascript" src="/js/common.js" />> but was:<<script language="javascript" src="/js/common.js" />>
    at junit.framework.Assert.fail(Assert.java:47)
    at junit.framework.Assert.failNotEquals(Assert.java:282)
    at junit.framework.Assert.assertEquals(Assert.java:64)
    at junit.framework.Assert.assertEquals(Assert.java:71)

期望的内容和实际内容是完全一致的,这个是因为后面的对象的toString方法的输出结果和前面的字符串确实是一样的,但是字符串和一个其它类型的对象进行equals比较一般都是返回false的。

Java很简单也很难

说Java简单,是因为它的语法,这个也是当初最吸引我的,我被C++的语法搞蒙了
但是,在实际的工作中,能够真正把Java使用得自如的人并不是很多,见到的大部分的人写代码没有问题,但是如果遇到一些问题就茫然了,不知道如何去解决问题,虽然有些人知道用google可以解决很多问题,但是有时候是因为自己的某些问题导致的问题,也有时候仅仅是开发环境的一些干扰导致的,很少有人和他遇到的问题完全一样。在这种情况下,就可以看出真正的功底了,对Java更加了解的人并不会急于解决问题,而是详细的查看出错信息,思考最有可能的原因。虽然就是简单的两句话,但是每一句都是建立在大量的经验的基础上的。
先说出错信息,我们知道Java的出错信息往往有很多冗余,在这么多信息里面怎么找到关键信息呢?加上有时候代码编写不规范,出错信息被吞掉了,或者配置的日志工具有问题,出错信息不输出到控制台,而是输出到日志文件。
再说最有可能的原因,你遇到的错误足够多了,你才能在脑海中搜索类似的问题,而且要保持一个开放的思想,因为错误信息稍微差一点,错误的原因可能就千差万别,还有时候信息一样也可能是不同的原因导致的。

有时候并不是很喜欢IoC/DI

相信现在做Java的人都应该听说过IoC/DI,也就是由容器负责类之间的初始化和组装,好处是很明显的,你不用自己写代码维护类之间的依赖关系,而且如果别人想用某个实现替换你的,也很简单,修改下配置文件就可以,但是有时候,例如你在看别人的源代码的时候,你想知道某个类谁在使用,很可能用IDE的工具是找不到的(例如Eclipse的References功能),但是这个类确实是被别的类引用的。
呵呵,当然,这个只是我发个牢骚,IoC/DI还是非常好的功能,对于中型以上的应用是非常有效的,对于小型应用可能就没有太大的必要了,反而影响别人读你的源代码。典型的例子就是Pluto,呵呵,一共就声明了9个Bean,这个关系用代码很好维护的。

Pluto的session问题

按照JSR168规范的说明,在Portlet中,我们可以通过PortletSession访问存储在HttpSession中的属性,我看了Pluto的实现,也确实是这样做的:
    // PortletSession Impl: Attributes —————————————–
   
    public Object getAttribute(String name) {
        return getAttribute(name, DEFAULT_SCOPE);
    }
   
    /**
     * Returns the attribute of the specified name under the given scope.
     *
     * @param name  the attribute name.
     * @param scope  the scope under which the attribute object is stored.
     * @return the attribute object.
     */
    public Object getAttribute(String name, int scope) {
        ArgumentUtility.validateNotNull("attributeName", name);
        String key = (scope == PortletSession.APPLICATION_SCOPE)
                ? name : createPortletScopedId(name);
        return httpSession.getAttribute(key);
    }

但是当我把pluto集成到我们的应用里面的时候,问题就来了,用户登录后,访问其它的页面和访问portlet的页面的时候的session id是不同的,历经磨难,找到一些前人的文章:

 
From testing with Jetspeed/Pluto on Tomcat 5.0 and the default configuration of 5.5, servlets accessed directly do not share the portlet session. In Tomcat 5.5 configured with emptySessionPath="true", behaviour is correct as described in the portlet specification.

So, to summarize what I’ve learned, if you need to pass session objects from a portlet to a servlet using Pluto as your servlet container, the following three conditions must all be true:

  1. You must run Tomcat 5.5.x, or later.
  2. You must set crossContext="true" in your web app’s element.
  3. You must set emptySessionPath="true" in the element in Tomcat’s server.xml file.

If you omit any one of these three steps, it won’t work.

所以问题的核心就是emptySessionPath,在我们的环境中使用的是tomcat5.0,crossContext是设置为true的。

2007年3月21日下午更新:
集成中的问题是我犯了一个错误,我在获取HttpSession中的属性的时候忘了应该使用APPLICATION_SCOPE,而不同的session id是因为我的另外的代码导致的(我写了一个Bridge类去获得Struts的Action的执行结果并输出,在Bridge中的请求相当于另外一个客户端)。
从Portlet中,使用RenderRequest..getPortletSession().getAttribute("xxxx", PortletSession.APPLICATION_SCOPE)就可以拿到其它的普通servlet和jsp中设置的值了。

Pluto admin在IE下不工作

这里的不工作是指添加删除按钮不工作,点击完全没有反应,解决办法是WEB-INF\fragments\admin\page\view.jsp,给button标签加上type="submit",这样按钮就有反应了(可能还会有其它的BUG,我是直接升级到这个文件的最新版本的),但是这不能解决全部的问题,因为添加按钮不能完全正常工作,你只能给第一个页面添加portlet,如果你选择了其它的页面,然后想用下面的添加按钮给那个页面添加portlet,结果是你的点击不会产生任何效果,因为后台prtolet认为是remove操作,但是没有选择要删除的portlet,解决办法是修改PageAdminPortlet:
        String availablePortlets = request.getParameter("availablePortlets");
        if(availablePortlets!=null) {
            doAddPortlet(request);
        }
        else {
            doRemovePortlet(request);
        }
这样做其实也是有问题的,就是如果你在下面选择了想添加的portlet,但是实际点击的是remove按钮,其结果和你预期的不同。

其实所有的这些问题都是源于Pluto不成熟,它只是给出了一个最最简单的参考,并没有经过充分的测试,而且它的开发人员可能是使用FF作为测试的主要浏览器的。

JSR168

JSR168规范是定义portlet容器及portlet之间的关系的,但是由于定稿比较早(2003年10月),所以没有涉及到portlet的独自渲染和刷新,而这个是现在的web2.0或者新一代web界面所不可或缺的,当然,新的JSR286规范可能会解决这个问题,但是我们不能等待这个规范的出台。就目前的情况看,融合jsr168和AJAX并不会很困难,问题的关键是没有一个轻量的组件或者成熟的解决方案,有的只是大的软件厂商在他们的portal产品中提供这种特性的支持。这段时间一直在用pluto,刚刚整合到我们的系统中,看了下pluto最终渲染出来的页面片断:
  <!– Assemble the rendering result –><br />
 
<div id="/testsuite.TestPortlet!764587357|0" class="portlet"><br />
  
<div class="header"><br />
      <!– Portlet Mode Controls –><br />
      <a href="http://localhost:8080/pluto/portal/__pm0x3testsuite0x2TestPortlet1!764587357|0_view"><span class="view"></span></a><br />
      <a href="http://localhost:8080/pluto/portal/__pm0x3testsuite0x2TestPortlet1!764587357|0_edit"><span class="edit"></span></a><br />
      <a href="http://localhost:8080/pluto/portal/__pm0x3testsuite0x2TestPortlet1!764587357|0_help"><span class="help"></span></a><br />
      <!– Window State Controls –><br />
      <a href="http://localhost:8080/pluto/portal/__ws0x3testsuite0x2TestPortlet1!764587357|0_minimized"><span class="min"></span></a><br />
      <a href="http://localhost:8080/pluto/portal/__ws0x3testsuite0x2TestPortlet1!764587357|0_maximized"><span class="max"></span></a><br />
      <a href="http://localhost:8080/pluto/portal/__ws0x3testsuite0x2TestPortlet1!764587357|0_normal"><span class="norm"></span></a><br />
      <!– Portlet Title –><br />
    
<h2 class="title">Test Portlet #1</h2>
<br />
    </div>
<br />
  
<div class="body"><br />
从这个输出来看,我们需要做的可能很简单,替换portlet-skin.jsp,重新定义那些按钮的输出为一个javascript,这样应该就可以部分刷新了。目前我们产品的其它一个功能已经可以做到这个了。现在的问题就是转换了。

更早的文章 更新的文章

© 2025 解惑

本主题由Anders Noren提供向上 ↑