年纪大了就喜欢回忆从前?
不。
人生又走到了未知的分岔路口,停下脚步回眸过去的路,正是为坚定向前跋涉的心。
过去通宵达旦努力研究的技术在现在回头看看都初级了点,但一个人处理问题的方式和态度不会过时。我心血来潮,花了不少时间来翻看工作日记和旧项目,力图总结一下工作5年的得失。殷鉴不远,可以前知也。
(二).混沌年代:JSP开发
前面讲到我是在地税组,除此外开发中心还有国税组和项目组,一二十个开发人员。我在地税组做了2年多,后来公司成立技术组,我脱离地税组成为技术经理。
地税组多的时候6、7人,少时2、3人,负责地税申报网站和一些相关项目的开发和维护,经常跑地税局。招我的时候,开发中心十几个程序员基本都是做php和delphi的,实话实说精通JAVA的一个也没有。从历史来看,以前3、4年一直用php做网站,用delphi做客户端,只是随着04年起J2EE平台在省税务行业逐渐占统治地位,公司才开始进行技术转型。一大批项目都要从C/S架构转向B/S架构、从PHP转到JSP,这是公司当时的技术大方向,我后来几年的个人发展其实是顺应了这个潮流——不过当时的我自然想不到这些。
大家都知道,技术平台转型是很危险的。虽然说船小好调头,但其实也没这么容易,招聘一批Java高手?No,公司一向的策略是招应届生,以旧带新能干活就行,便宜嘛。而且最好是本地人,否则一年半载就跑掉的概率太高。也没有什么培训,完全靠自学。这是内部现状,外部呢?那时候电子政务还没兴起多少时间,税务局离成熟的客户还差得远,那时候往往是开一个会,说一堆模模糊糊的要求,然后给你一个最后交付时间,等你开发了一半会突然来个电话说:“唉呀,我又想起来还要什么什么”。而且他们会认为什么功能都很简单,“明天给我行不行?”,听到这种话就考验你的忽悠能力了。不像现在,国地税已经熟悉了软件开发的基本规律,甚至会自己写好需求文档再来跟你讨论。强势又不成熟的客户会让你疲于奔命,一定要学会拒绝客户的不合理要求并且把守住需求边界!这我深有体会。
加上与中国大多数软件公司一样,这是个不讲究技术(实际也没多少技术含量)和代码质量的小公司,他的理念是服务至上(客服人员与程序员之比是1:1强),而实际上的生存之道是要与相关部门保持良好的关系,这才是首要的。因此技术微不足道,至少在老总眼里如此。你只要实现功能,谁也不来管你的代码是不是把一坨代码复制粘贴到另一处。很自由,是不是?
在这种情况下,结果你也猜到了:从原先就很糟糕的php转换到对技术要求更高的J2EE,结局只会更加糟糕。开发效率低下,性能低下,天天忙着补漏救火。而我这个新丁纯粹出于个人的技术追求,无意中起到了不少正面作用,才脱颖而出,于2年多后升到公司的中层之一——技术经理。
转型过程比较缓慢。
首先用jsp重写的产品就是地税申报网站。05年3月份我进入地税组时,这个工作已经由地税组经理老唐完成。如前所述,我用了大概一个月学会了jsp开发——完全用php方式写的jsp,唯一的开发工具是UltraEdit,服务器是Weblogic,数据库用Sybase+PowerBuilder。这时候我完全是学习阶段,学sql学html学js学css学业务规则学xxx,什么都要学,没有文档没有培训,自己看着工程代码琢磨,实在搞不定就去请教老唐——被戏称第一调试高手的老唐对用out.print抓到bug实在是很有心得,令我获益匪浅。
1.连接池泄漏
show一下我们那时候的精彩代码,仔细看看,你能从中发现多少问题?
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>用户信息确认</title>
</head>
<body bgcolor="#F6F6F6" onLoad="fix()" onScroll="fix()" onResize="fix()">
<%@ include file="wsqz.jsp" %>
<%
DBConnect conn=new DBConnect();
String sql = "select xxx from ddd where id = ?";
conn.setPstmt(sql);
conn.setString(1,sessUserID);
ResultSet result=conn.executeQuery();
//$result = sybase_query($sql);
//$row = sybase_fetch_row($result);
%>
请确认您的用户信息:
<table width="100%" align="center" border="0" cellpadding="0" cellspacing="0">
<tr>
<td height="20"></td>
</tr>
</table>
<table width="100%" align="center" border="0" cellpadding="0" cellspacing="0">
<tr valign="middle">
<td >纳税人税号:</td>
<td><%=result.getString(1)%> </td>
</tr>
<tr>
<td> 微机代码:</td>
<td><%=result.getString(2)%></td>
</tr>
<tr>
<td>纳税人名称:</td>
<td><%=result.getString(3)%></td>
</tr>
</table>
<table width="100%" align="center" border="0" cellpadding="0" cellspacing="0">
<tr>
<td height="20"></td></td>
</tr>
</table>
请确认您。。。<br>
联系电话:xxx
<%
result.close();
conn.close();
%>
</body>
</html>
这批jdbc代码后来直接导致weblogic在高峰期频频崩溃假死,老唐天天盯着weblogic的监控手动重启。客户投诉暴涨,不管服务部还是开发部都承受着巨大的压力,请bea的工程师远程会诊也没查出所以然来,还是老唐放狗搜索发现这会导致连接池泄漏。我接到任务开始改写所有的jdbc代码,给他们加上try..catch..finally..close ,大概几千处,足够我加班干个几天几夜的。不行,我决定发挥程序员的懒惰,写个程序自动修改。
怎么写呢?页面很杂乱,嵌套多得要死,代码风格也多,没有时间写一个面面俱到的独立程序来兼容所有可能性,我决定以一种所见即所得的修改方式来最快完成任务——用jEdit写一个macro 。
那段时间我正好研究了几个月jEdit和beanshell,打开jEdit的API边查边写:
/*
*/
// beginning of Run_Make_New_JSP.bsh
public ArrayList getConnList(){
ArrayList list = new ArrayList();
int index = 0;
String text = textArea.getText();
while((index = text.indexOf("= new DBConnect();",index))>0){
//reference name
String text = textArea.getLineText(textArea.getLineOfOffset(index));
StringTokenizer tok = new StringTokenizer(text,"= tnr");
if(tok.nextToken().equals("DBConnect"))//reference name
list.add(tok.nextToken());
index = index+20;
}
return list;
}
public String[] getRsArray(String connName){
int index = 0;
String[] a = null;
ArrayList al = new ArrayList();
//find rs line
String rstmp = "= " + connName + ".executeQuery";
while((index = textArea.getText().indexOf(rstmp,index)) > 0) {
lineOfRs = textArea.getLineOfOffset(index);
//token
String rsLineText = textArea.getLineText(lineOfRs);
tok = new StringTokenizer(rsLineText,"= trn");
tok.nextToken();
rsName = tok.nextToken();
al.add(rsName);
index+=rstmp.length();
}
if(al.size()==0)
return null;
else
{
a = new String[al.size()];
Iterator it = al.iterator();
for(int i=0;i<al.size();i++)
{
a[i] = (String)(it.next());
}
}
return a;
}
public int[] getLineOf(String word)
{
int index = 0;
ArrayList al = new ArrayList();
String text = textArea.getText();
while((index = text.indexOf(word,index))>0){
al.add(textArea.getLineOfOffset(index));
index = index+word.length();
}
int[] a = new int[al.size()];
Iterator it = al.iterator();
for(int i=0;i<al.size();i++)
{
a[i] = (int)(it.next());
}
return a;
}
//按照被替换行的缩进进行替换,每一行的缩进与前一行相同,但最后一行有点小问题
public void replaceLines(int line,String t){
int offset = textArea.getLineStartOffset(line);
String text = textArea.getLineText(line);
i=0;
String pre = "";
while(text.charAt(i)=='t'||text.charAt(i)==' ')
{
if(text.charAt(i)=='t') pre+="t";
if(text.charAt(i)==' ') pre+=" ";
i++;
}
textArea.setCaretPosition(offset);
textArea.deleteLine();
buffer.insertIndented(offset,pre+t);
}
// main routine
public void makeOne(String connName) {
//Macros.message(view,""+map.size());
int lineOfConn = 0, lineOfRs = 0,lineOfRsClose = 0,lineOfConnClose = 0;
int lineOfConn = getLineOf(connName)[0];
das = getRsArray(connName);
if(das!=null)
{
String rsName = getRsArray(connName)[0];
//DBConnect conn = ...
String j = "DBConnect "+connName+" = null;rnResultSet "+rsName+
" = null;rntry{rn"+connName+" = new DBConnect();rn";
replaceLines(lineOfConn,j);
//ResultSet rs = ...
int rsn = getLineOf("ResultSet "+rsName+" = "+connName)[0];
rsnn = textArea.getLineText(rsn);
suffix = rsnn.substring(rsnn.indexOf(rsName));
replaceLines(rsn,suffix+"rn");
//delete rs.close()
//textArea.setCaretPosition(textArea.getLineStartOffset(getLineOf(rsName+".close()")[0]));
//textArea.deleteLine();
replaceLines(getLineOf(rsName+".close()")[0],"");
//conn.close();
String d = "}rn"+
"catch(Exception e)rn"+
"{rn"+
" e.printStackTrace();rn"+
"}rn"+
"finallyrn"+
"{rn"+
" tryrn"+
" {rn"+
" if("+rsName+"!=null)rn"+
" "+rsName+".close();rn"+
" if("+connName+"!=null)rn"+
" "+connName+".close();rn"+
" }rn"+
" catch(Exception e)rn"+
" {rn"+
" e.printStackTrace();rn"+
" }rn"+
"}rn";
replaceLines(getLineOf(connName+".close()")[0],d);
//Macros.message(view,connName+getLineOf(connName+".close()")[0]);
}else{
//DBConnect conn = ...
String j = "DBConnect "+connName+" = null;rntry{rn";
replaceLines(lineOfConn,j);
//conn.close();
String d = "}rn"+
"catch(Exception e)rn"+
"{rn"+
" e.printStackTrace();rn"+
"}rn"+
"finallyrn"+
"{rn"+
" tryrn"+
" {rn"+
" if("+connName+"!=null)rn"+
" "+connName+".close();rn"+
" }rn"+
" catch(Exception e)rn"+
" {rn"+
" e.printStackTrace();rn"+
" }rn"+
"}rn";
replaceLines(getLineOf(connName+".close()")[0],d);
}
}
// this single line of code is the script's main routine
// it calls the methods and exits
if(buffer.isReadOnly())
Macros.error(view, "Buffer is read-only.");
else{
ArrayList list = getConnList();
it = list.iterator();
while(it.hasNext())
{
makeOne((String)(it.next()));
//sa+=" "+(String)(it.next());
}
it = list.iterator();
sa = "";
while(it.hasNext())
{
//makeOne((String)(it.next()));
sa+=" "+(String)(it.next());
}
Macros.message(view,sa);
}
/*
Macro index data (in DocBook format)
<listitem>
<para><filename>run_Make_New_JSP.bsh</filename></para>
<abstract>
<para>
Adds user-supplied <quote>prefix</quote> and <quote>suffix</quote>
text to each line in a group of selected lines.
</para>
</abstract>
<para>s
Text is added after leading whitespace and before trailing whitespace.
A dialog window receives input and <quote>remembers</quote> past entries.
</para>
</listitem>
*/
// end Add_Prefix_and_Suffix.bsh
这是我首次为开发写辅助工具,马上就尝到了甜头。只要用jEdit打开jsp文件按一下快捷键,rs、conn等就自动改成了关闭形式,我可以立刻检查改动是否正确。我感到很自豪,避免了几万次枯燥无味的copy/paste,嘿嘿,这才是程序员的做事风格!虽然写macro用了一天时间,但仍然比手工修改要合算得多,不但省力,更避免了引发新的错误——以我的经验,机械麻木的copy/paste过程必然引发忘改变量名之类的低级问题。
后来我写了一些jEdit的应用经验在blogjava 上(留言里有pi1ot ,他是个jEdit铁杆,甚至为jEdit修改了一套字体,前段时间还在javaeye发了个jedit4.3的新闻 ),本来还想写高级篇,但是随后杯具——某天我没有关机导致compaq笔记本硬盘在颠簸中报废了——大量代码永远安息,也包括我为jEdit写的十几个bsh。我很受伤,兴致缺缺,这个计划就搁到脑后了。
本文回顾了从PHP转向JSP技术过程中遇到的问题与挑战,包括连接池泄漏、客户需求变动及技术转型困难等。作者通过编写辅助工具解决了连接池泄漏问题,并分享了这一过程。
9337

被折叠的 条评论
为什么被折叠?



