凡是搞软件开发的都得和用户面对面交流,了解需求。运气好的时候用户比较有水准,一开始就能给你个很详细的文档。不过一般来说没有那么理想的情况。就算有,还是得对着文档和用户好好交流一下。项目开始之前,先从其他角度,通过文档或者类似的系统了解一下大概的业务和工作流程,是无可厚非的。根据自己的理解进行整理,可以作为需求调研时的辅助,可以事半功倍。按理来说,了解需求的时候,我们都是听众,用户是讲师,可总有些实际情况不是这样。

      有那么一种人,总是在嘴上挂着“从用户角度出发考虑问题”,总以为自己是业务领域的专家(有时嘴上不一定这么说),凡是都要我们先假设一下用户的流程。当然我不是说假设一下用户的流程有什么问题。有时候,适当分析,假设一下用户的业务流程,能方便我们需求调研时引导、帮助用户更加清晰地表达出自己的意向。本来了解需求嘛,让用户多说说,把他的想法和疑惑都表达出来,不就结了。可是就有那么些人喜欢和用户比口才,只要能插得上嘴的地方都要出来添油加醋一番,那架势比参加演讲比赛还精神。

      总有些项目领导做了一番假设之后,自信满满地来到用户面前,在用户介绍业务的时候不断打岔,时不时跳出来“wait,这里我补充一下”,“**,你要注意一下,这个地方很重要和你负责的模块很有关”,“这个术语容易造成误解,我们改叫**吧”……个人觉得,这种做法其实也算一种不尊重用户的表现,频繁打断别人的说话增加自己的补充或是理解,并不见得能让同行的开发人员对需求有更深的理解,甚至有时还会误导用户。个人认为,最好的方式是耐心听完用户的介绍后,项目组成员进行讨论,然后各个开发人员有针对性地去了解自己的问题。如果项目负责人真的有那么深入的理解,也应该在小组讨论时,引导具体的开发人员,去提出自己的问题。这样既可以让用户觉得心理舒坦,也可以让开发人员更充分了解了用户的思路。

      还有一种人,虽然知道自己不是业务专家,但还是要按照自己的既定思路,和用户狂侃,似乎抱着一种“如果我说不出点什么,用户就会觉得我没有用心做事”的想法,一定要从这里引申到那里,从现在说到将来。当然,我不是说这种引申有什么不好,还是那么说,得有个度。做企业信息化,有些团队可能已经有了自己的积累,除了拥有领域专家外还有一定的技术沉淀,所以一听到用户的需求,就可以告诉用户“您所说的问题我们已经有了一个很好的解决方案,而且如果您……会带来……好处”。对于刚起步的团队,一开始就好高鹜远,一心想着借此机会一仗成名,生怕漏了什么没说显得自己技不如人。用户可能很开心,因为他很开心“物超所值啊,我就给了这么点钱,也有人愿意做这么多工作。”不过呢,言多必失,不了解业务的情况下,指不准原形毕露,用户的心立马凉半截“完了”。作企业级的东西,再小也不可能一步到位,一次成型。可怜的开发人员,本来就面临严峻的挑战,突然间被引申出去不少,更加郁闷。到了交货时间,继续拖吧,告诉用户“由于……所以……开发过程中有很多困难,希望再延期一下”。于是乎,恶性循环开始。在原定时间内,增加了一些新的任务,时间更紧了;某些用户被误导或是被灌输后的临时想法,发现和实际业务不一致,需要修改;项目延期,赶工使bug增加。

      最可恶的就是之前说的那种喜欢臆测用户需求的人(这种人其实心里老觉得说不出点啥就会让用户觉得没用功或做不好事情)。这种人当上领导,项目组肯定遭殃。有事没事就喜欢召集项目组成员“我们现在来讨论一下。”然后就开始他的长篇大论,虽然图文并茂,说得头头是道,但就是没有经过考证。你想和他提点意见,说这个东西可能不用这样,他还会说肯定是这样。最有意思的是他嘴上还会说“我们都不是专家,先假设一下”,那还不如直接去和用户交流来得快呢。再碰到糟糕点的项目组负责人,根本不管什么用户了,按照领导的假想满头大汗狂写了一阵,还不三不四,直到过了n久以后要验收了还有很多流程还没有真正由业务人员使用过或者有些一经使用就被咔嚓了。但是往往越是这种情况下,一边开发,一边还能加近些领导自己的想法。试想这种项目要到什么时候才是个尽头啊。我接触的最典型的是,开发组在抱怨,用户也在抱怨;开发组希望早点验收,摆脱这个包袱;用户希望早点验收,然后把这个破烂玩意丢到一边去。(当然这个可能是我说得严重了些)

     软件开发是一个迭代的过程,不论多小的项目都不应该抱着一次成型、一步到位的心理。用土一些的话说:如果能一次就把软件做好,还赚什么钱啊,有些功能可以日后升级时候再加,小改不要钱,大改收点升级费。说得动听些的就是:即使是用户本身,他也不一定就是领域专家,他也在信息化的过程中逐步完善自己的理解,不可能一次就提出所有正确的需求;开发人员同样不是领域专家,即使是,也不可能将一个企业的业务套到另一个企业上;开发人员和用户在开发过程中都不断补充自己,提高自己的认识。

     软件开发所要做的事情其实很简单:1、跑到用户那里认真当听众,不要怕用户怎么看,不懂就问。2、完全按照用户的需求先实现原型(不管做出来的东西是不是很简陋)。3、带着原型再到用户那里当听众,让用户来批斗。4、修改。5、重复3和4,直到项目结束。

     最后声明:本文纯属发发牢骚,最近做得真有些郁闷了。希望偶的牢骚可以给大家提个醒,不要在自己去了解需求时也去显示自己的口才。文中有什么不妥之处望大家指正。
posted @ 2008-05-07 23:33 陈鹏(偶是坏人) 阅读(1644) | 评论 (12)编辑

之前描述了问题和想法,现在开始讨论问题的解决方法。当然,这里给出的只是我个人的想法,希望路过的朋友多提宝贵意见。

      首先,再来确定一下要解决的问题:“MIS系统中,总需要进行查询,而且往往都是多表关联查询。但是,并不是所有表都需要进行Join,只有当用户选择了某一个表的字段作为条件时,才对所选择的表进行Join。要做到这一点,就需要开发人员自己利用if语句进行判断,但是这在表多时,编写代码很容易出错。所以很多情况下,开发人员都选择了一次性在SQL语句中写入所有可能用到的Join,但这又带来了性能上的问题。是否能有一种工具能将所有需要的字段、联结、分组、排序都定义好,由工具自动根据过滤条件选择所需的联结生成SQL语句?”下面,我将把我自己的解决方法告诉大家。



             对象图(时间问题,为了方便理解,简单画一个放上,下次有空了我再补充)

      对象的简要说明:
          1、QueryInfo:查询的信息。
          2、Fields:字段的集合。
          3、Field:单个字段。
          4、Joins:联结的集合。
          5、Join:联结。
          6、Filters:过滤条件的集合。
          7、IFilter:过滤条件。
          7、Filter:单个过滤条件。
          8、Relation:关联条件。
          9、FilterGroup:一组IFilter(用“()”)。
          10、GroupBys:GroupBy的集合。
          11、GroupBy:Group By。
          12、Having:Having子句。
          13、Exists:Exists子句。
          14、OrderBys:OrderBy的集合。
          15、OrderBy:Order By。
          16、SubQuerys:子查询(Query)的集合。
          17、SimpleParameterInfo:参数的简单信息。
          18、QueryBuilder:查询生成器。

      根据之前的叙述和问题的描述,我们可以很快确定,解决问题的先决条件是“所需的联结”。为了确定“所需的联结”我们就必须先确定“所需的表”。在SQL语句中表在哪些地方定义呢?一般都是写在From后面,仔细观察一下From后面的表名实际上完全来自查询中的字段、过滤条件、排序、分组,所使用的表,比如:select TABLE1.FIELD1, TABLE2.FIELD1 from TABLE1, TABLE2 where TABLE1.PK = TABLE2.FK and TABLE1.FIELD1 = 'VALUE'或select TABLE1.FIELD1, TABLE2.FIELD1 from TABLE1 left join TABLE2 on TABLE1.PK = TABLE2.FK where TABLE1.FIELD1 = 'VALUE'。Fields、Exists、Filters、GroupBys、Having、OrderBys部分所使用到的表都很容易确定,又因为一个查询语句里的Join只有一个主表,所以根据Join的从表就很容易确定是否应该使用了。但是,只这样还不够。因为之前的例子我们可以看到根据SITE联结时,还必须先与PROJECT建立联结,否则会出错。所以,在Joins里还维护了一个JoinRefrencesTable(表示Join互相引用情况。我偷懒,直接用Dictionary<>)。利用前面说的方法和JoinRefrencesTable就可以很容易生成SQL语句所需的Join部分了。

      搞定了Join,剩下一个问题就是子查询。当Filter使用In操作付时可能使用子查询,Join的也可能用子查询,Exists子句也是个子查询。因为子查询也是一个QueryInfo,所以我采用的是利用QueryInfo.Alias引用的方法。因此,这里还需要判断一下,所使用到的子查询。最终生成SQL语句的时候,我目前是根据标记,对子查询进行替换。当然这个部分就简单多了。

      差点漏掉了一点,这里还有一个问题,就是之前所说用DbParameter解决不同数据库的差异问题。这一点主要针对的是Filter,就好像Oracle和Access的日期型字段在查询时的差异,Oracle必须写成“to_date(日期, 格式)”而Access则用“#日期#”。如果用DbParameter就可以很容易解决这个问题。我所使用的方法是对Filter中除了like和子查询之外的Value1、Value2、Values,根据其DataType定义,生成DbParameter,然后再SQL语句中相应的位置生成一个标记,最后生成的时候,再进行替换。这么做的理由如下:1、对于不同的数据库该如何创建参数名我不知道,这里只能由用户自己在QueryBuilder中定义。2、OracleClient和SqlClient中的DbParameter.Name支持自定义名称。但是,不少OleDb驱动却没有这个功能,都只能用“?”来。3、因为OleDb中很多DbParameter.Name必须使用“?”,所以最终生成的时候很可能还不需对所有的DbParameters进行排序,这样才能正确生成所需的DbParameter。因为这三点,QueryInfo生成的只是原始的、带有标记的SQL语句。QueryBuilder则根据数据驱动,修改成真正可用的SQL语句(GetFixedSqlStqtement方法)。

      最后,总结一下完整的生成SQL语句的流程(时间问题,就不上流程图了,用文字简单描述一下):
          1、检查Fields中是否为空,不为空则生成“select 字段1,字段2,字段3……”,为空则生成“select *”。将所使用的表记录下来。如果Field.Function不为空则添加方法,方法的参数由FuncArgs定义。将FuncArgs中的“{f}”替换为Field.Name(用于实现Decode、Length之类的方法)。
          2、检查Exists和Filters,不为空则生成where语句,并在生成的where语句中对参数和子查询进行标记。将参数值、所使用的表和子查询记录下来。为了方便,使用先Exists后Filters的方法。当Filter在Filters的第一位时,判断PrevLogic是否为Not,是则生成“not FIELD = VALUE”否则忽略PrevLogic。
          3、检查GroupBys是否为空,不为空则生成group by部分。然后检查Having是否空,不为空则生成having子句。如果GroupBys为空则跳过Having的判   断。将所使用的表和子查询记录下来。
          4、检查OrderBys是否为空,不为空则生成order by部分。将所使用的表记录下来。
          5、根据所使用的表和JoinRefrencesTable确定使用的Join。将所使用的表和子查询记录下来。如果使用的Join为空,则直接生成“from TABLE1,,TABLE2”生成,否则生成“from MASTER join SLAVETABLE on MASTERTABLE.FIELD1 = SLAVETABLE.FIELD2”。
          6、将所有的部分合并,将子查询标记替换为“(QueryInfo.ToSQL()) QueryInfo.Alias”。
          7、最终使用时,QueryBuilder根据用户定义的参数前缀生成DbParameter,如“@”、“:”+生成自动编号或直接用参数前缀(针对OleDb数据驱动)。

      以下是实际使用中的例子,下面这个是通过编程方式定义QueryInfo:

private QueryInfo QueryDefine
{
    
get
    {
        Join join 
= null;

        
//定义主查询
        QueryInfo queryDefine = new QueryInfo("SITE_FACILITY");

        
//定义字段,全部则用“*”,否则写具体的字段名
        queryDefine.Fields.Add(new Field("*""FACILITY"));

        
//定义Join
        queryDefine.Joins.MasterTable = "FACILITY";

        join 
= new Join("SITE", JoinTypeCode.LeftOuter);
        join.Add(
new Relation("SITE""ID""PROJECT""SITE_GUID"));
        queryDefine.Joins.Add(join);
        join 
= new Join("PROJECT", JoinTypeCode.LeftOuter);
        join.Add(
new Relation("FACILITY""MASTER_GUID", join.SlaveTable, "ID"));
        queryDefine.Joins.Add(join);
        join 
= new Join("FACILITY_CATALOG", JoinTypeCode.LeftOuter);
        join.Add(
new Relation("FACILITY""CATALOG_GUID", join.SlaveTable, "ID"));
        queryDefine.Joins.Add(join);

        
return queryDefine;
    }
}

另一种以XML定义的形式:

<QueryInfo Alias="SITE_FACILITY">
    
<Fields Distinct="False">
        
<Field Name="*" Table="FACILITY" ></Field>
    
</Fields>
    
<Joins MasterTable="FACILITY" >
        
<Join JoinType="LeftOuter" SlaveTable="SITE" >
            
<Relation PrevLogic="And" MasterTable="SITE" MasterField="ID" Operator="=" SlaveTable="PROJECT" SlaveField="SITE_GUID" ></Relation>
        
</Join>
        
<Join JoinType="LeftOuter" SlaveTable="PROJECT" >
            
<Relation PrevLogic="And" MasterTable="FACILITY" MasterField="MASTER_GUID" Operator="=" SlaveTable="PROJECT" SlaveField="ID" ></Relation>
        
</Join>
        
<Join JoinType="LeftOuter" SlaveTable="FACILITY_CATALOG" >
            
<Relation PrevLogic="And" MasterTable="FACILITY" MasterField="CATALOG_GUID" Operator="=" SlaveTable="FACILITY_CATALOG" SlaveField="ID" ></Relation>
        
</Join>
    
</Joins>
</QueryInfo>

执行查询的代码如下:

public TFacilityTable Select_ByFilterCollection(TccFilterCollection filters)
{
    TFacilityTable table 
= new TFacilityTable();

    QueryInfo qi 
= QueryDefine;
    
//由于系统是之前完成的,前台的搜索控件并没有和QueryInfo结合,所以在qi.Filters是在最后才添加的。如果直接和表现层控件邦定则可以省掉下面for循环
    for(int i = 0; i < filters.Count; i++)
    
{
        
if(filters[i].Checked)
        
{
            Filter c 
= new Filter();
            TccBaseFilter f 
= filters[i];
            c.Field.Table 
= f.TableName;
            c.Field.Name 
= f.FieldName;
            c.Operator 
= ParseOpera(f.Opera);
            c.Value1 
= f.m_Value1;
            qi.Filters.Add(c);
        }

    }


    QueryBuilder qb 
= new QueryBuilder(qi);
    GMIS.DataAccess.Context.DbContext dbContext 
= null;
    
if(DbContext.DbConnection is OleDbConnection)
    
{
        
//将QueryBuilder设置为针对Access数据库的
        qb.AutoParameterNumber = false;
        qb.ParameterPrefix 
= "?";
        dbContext 
= new GMIS.DataAccess.Context.DbContext("System.Data.OleDb", DbContext.ConnectionString);
    }

    
else
    
{
        
//将QueryBuilder设置为针对Oracle数据库的
        qb.AutoParameterNumber = true;
        qb.ParameterPrefix 
= ":";
        dbContext 
= new GMIS.DataAccess.Context.DbContext("System.Data.OracleClient", DbContext.ConnectionString);
    }

    qb.FillDataTable(dbContext, table);
    
return table;
}

根据filters参数的不同,自动生成的SQL语句如下:
      1、什么都未选择

select FACILITY.* from FACILITY 

2、选择了站点时

select FACILITY.* from ((FACILITY left outer join PROJECT on FACILITY.MASTER_GUID = PROJECT.ID ) left outer join SITE on SITE.ID = PROJECT.SITE_GUID ) where SITE.NAME = :p1 

3、选择了站点、工程名称、设备名称、设备编目时

select FACILITY.* from (((FACILITY left outer join PROJECT on FACILITY.MASTER_GUID = PROJECT.ID ) left outer join SITE on SITE.ID = PROJECT.SITE_GUID ) left outer join FACILITY_CATALOG on FACILITY.CATALOG_GUID = FACILITY_CATALOG.ID ) where FACILITY_CATALOG.CN_NAME = :p1 and FACILITY.CN_NAME = :p2 and PROJECT.CN_NAME = :p3 and SITE.NAME = :p4

4、选择了站点和设备编目时

select FACILITY.* from ((FACILITY left outer join PROJECT on FACILITY.MASTER_GUID = PROJECT.ID ) left outer join FACILITY_CATALOG on FACILITY.CATALOG_GUID = FACILITY_CATALOG.ID ) where FACILITY_CATALOG.CN_NAME = :p1 and PROJECT.CN_NAME = :p2 

从上面的代码可以看出,定义了QueryInfo之后,不需进行判断,只需传入需要的的过滤条件、针对的数据库类型,参数的前缀,即可生成SQL语句。这从一定程度上减少了开发员进行判断生成SQL语句的麻烦也避免了一次性写入所有Join所带来的性能问题。对于实现用户自定义条件查询,也可以带来一定的便利。
      目前存在的问题:
          1、QueryInfo的定义代码还是有些麻烦。不过定义的代码方面可以很容易做一些简化,还可以考虑使用生成器自动生成。
          2、生成SQL的代码没有进行优化,效率还没达到最优。这个可以改进,也不难。
          3、还没有做到针对不同的数据库,进行一些有针对性的优化。这个问题就需要认真考虑一下了。

posted @ 2008-04-27 02:05 陈鹏(偶是坏人) 阅读(1891) | 评论 (20)编辑
新增功能:
      方法名混淆,并可设置排除或混淆指定前缀的方法。
      变量名混淆时也可设置排除或混淆指定前缀的方法。

尝试:
      寻找新的字符串混淆方法。
      寻找是否有更加有效的流程混淆方法。
posted @ 2008-04-24 01:25 陈鹏(偶是坏人) 阅读(55) | 评论 (0)编辑

      MIS系统中最麻烦的查询不再于总共有多少个Join,而是在于确定需要多少个Join。用户什么条件都不选择时,最佳的做法就是直接对FACILITY表进行查询而不做任何Join;当用户选择了站点的条件时你除了必须加FACILITY表和SITE表的Join外,还必须先写上FACILITY表和PROJECT表的Join;用户同时选择了站点和设备编目时,除了FACILITY表 Join PROJECT表、FACILITY表 Join SITE表外,还得写上FACILITY表 Join FACILITY_CATALOG表。联结最多的情况就是用户同时选择了站点或CID以及设备编目的时候。而且这种确定需要多少个Join的问题也不是建一个视图能解决的。
 
      开发MIS系统时,开发人员的主要精力应放在关注业务上,但在实际工作中,许多繁杂的事情都分散了开发人员的注意力。特别当一个团队的综合实力不强,新手或是初入门者居多时,问题更为显著。要么,为了图省事,全写上去,影响了系统的性能;要么就为了平凑出一个正确的SQL语句费尽心思。虽然如今有很多优秀的框架,实现了这样那样的功能,从一定程度上简化了开发人员的工作,但还是没能把开发人员真正的解放出来。现在正在讨论的用面向对象的思想处理查询,很多框架都已经实现了。可是本人观察了几个国内、国外的框架,以及一些书籍的例子代码后发现,这些实现都只是根据用户定义生成SQL语句,却无法做到优化生成的SQL语句。这里插一下我个人的观点:“如果只是原封不动生成SQL语句,还不如自己手写来得简便。”

      经过一段时间的思考和总结后,我得出了如下结论:1、如果可以有一个自动化或半自动化的工具,只要输入所需的字段、条件、联结、分组、排序等,就可以替我们完成SQL语句的生成工作,必然能使开发人员有更多的时间去关注业务。2、如果1的方法可行,必然可以更进一步对这个工具进行优化,使其可以根据具体的数据库自动生成最优化的SQL语句。一方面可以提高新手的开发速度,另一方面还可以解决SQL语句造成的系统性能问题。3、如果可以有一个更加全面的框架或者平台,它不一定十全十美,但是可以完成一些重复、繁琐的工作,开发的效率同样可以得到提升。在使用的过程中,可以不断完善这个框架或者平台。可以这样设想,随着框架或者平台的不断完善,原先那些重复、繁琐的代码将不再出现,出错的几率也大大降低,这也意味着开发效率的进一步提高。当然,这些就是后话了。

      回到主题。上面说了一大堆,可能让人更云里雾里。简单以之前那个数据结构为例简单说明一下:首先,根据查询的目标和具体的需求,将所有需要输出的字段和Join定义好(先不考虑其他的咚咚,如:GroupBy、Having、OrderBy等等)。接着,点击“搜索”按钮的时候将用户定义的条件加入到原先定义的查询中。最后,自动根据条件和输出的字段,选择所需的Join,最终组合成SQL语句输出。

      由于不同的数据库所使用的数据类型不同,使用SQL语句查询时的语法也不同,所以针对Oracle数据库的代码直接搬到Access上就可能出错。最好的解决办法就是使用DbParameter,这样不仅可以解决不同类型数据库的问题,还可以在一定程度上解决SQL注入的问题。也可以算是一举多得吧。

posted @ 2008-04-24 01:16 陈鹏(偶是坏人) 阅读(1763) | 评论 (7)编辑

      MIS系统中的数据查询时必不可少的。而且大部分都是多表查询,这时候SQL语句中的Join就显得尤为重要。再加上现在的O-R mapping,更是给查询带来了一个不大不小的麻烦(我不是说O-R mapping的坏话)。以下的部分将用一个本人认为很典型的例子说明一下。

      数据库结构如下:


      用户界面如下:

      由数据库结构和界面的grid可以很容易想到,直接写上

select FACILITY.*, PROJECT.CODE as PROJECT_CODE, PROJECT.CN_NAME as PROJECT_CN_NAME, SITE.NAME as SITE_NAME, SITE.CID as SITE_CID,
FACILITY_CATALOG.CN_NAME as FACILITY_CATALOG_NAME 
from FACILITY left outer join PROJECT on FACILITY.MASTER_ID = PROJECT.ID left outer join SITE on PROJECT.SITE_GUID = SITE.ID left outer FACILITY_CATALOG on FACILITY.CATALOG_GUID = FACILITY_CATALOG.ID

。然后在后头写上where搜索里选择的条件。这似乎没什么难度吧。

      这个项目典型的地方是使用了一个可爱的框架,绑定在grid上的不是DataTable而是一个EntityCollection,而且这里还用了延迟加载,即使在SQL语句中写上了PROJECT.CODE, PROJECT.CN_NAME,由于延迟加载的关系都被忽略了(Facility这个实体对象里包含MASTER_ID,利用延迟加载来获取实体对象Project)。所以SQL语句就得写成了

select FACILITY.* from FACILITY left outer join PROJECT on FACILITY.MASTER_ID = PROJECT.ID left outer join SITE on PROJECT.SITE_GUID = SITE.ID left outer FACILITY_CATALOG on FACILITY.CATALOG_GUID = FACILITY_CATALOG.ID

至于后面的条件还是和前面一样加。我们可以想象一下这么做的问题:1、虽然用户只选择了站点这个条件,但是FACILITY和FACILITY_CATALOG进行了Join。2、如果用户只选择了设备编目,SQL语句就多了FACILITY和PROJECT、SITE的Join。当然啦,看到这里有人可能会说,我们用的框架没有这个问题或者你不会自己用if做判断啊。换框架没有必要,这里只是拿出一个典型的例子,相信就算换了框架,在开发过程中还是会遇到类似问题的。至于用if进行判断,同样也有麻烦,这个例子里的表比较少,写起判断没什么太大问题。关键是用户选择工程名称时你得加入FACILITY和PROJECT的Join,用户选择站点名称时你除了加入FACILITY和SITE的Join外,还必须先添加FACILITY和PROJECT的Join,否则就出错没商量。

      不知大家看到这个例子的特殊性没?本人见过一个项目组用这个框架的时候,一个SQL语句里一次性写了28个Join(自己汗一下先,怎么设计数据库的,虽说是该有不该有的都写了,也没有这么多吧)。当然这不排除数据库设计和框架的问题,其中还有一部分原因就是懒人多,表多条件多的时候谁都不喜欢一个if一个if拼SQL语句。说了这么多,总结起来就是“按需Join的问题”。如果能有一个工具能实现,一次性添加所需的所有东西,然后自动根据需要生成SQL语句,该多好(或者说方便多了,至少本人是这么认为的)?

      如果SQL语句进行分解。可以划分为显示的字段定义、表关联定义、条件定义、排序、分组等几个部分。因此,本人对SQL语句的各部分进行如下划分,并以以下术语命名:
      1、 Fields:输出字段的定义。
      2、 Joins:表关联定义。
      3、 Filters:查询条件定义。
      4、 GroupBys:分组定义。
      5、 Having:Having子句
      6、 Existis:Exists子句。
      7、 OrderBys:排序定义。
      8、 SubQuerys:子查询定义
      9、 UsedTables:查询中所使用的表。
      10、UsedJoins:查询中所使用的联结
      11、 QueryInfo:一条完整的SQL查询语句。
      12、 QueryBuilder:查询生成器。

因此SQL语句可以用如下形式表示:
select (Fileds) from (UsedTables | SubQuerys| UsedJoins] [where [Exists] Filters] [group by GroupBys] [having Having] [order by OrderBys]
注:黑体字表示SQL保留字。“()”表示必填项。“|”表示可在其中选择一项。“[]”表示可选项。

      经过分解后可以得出这样的结论,UsedTables中的表由所有的Fields、Exists、Filters、GroupBys、Having、OrderBys中所使用的表组成,SubQuerys也可以根据Alias从UsedTables判断是否使用。UsedJoins也可以用同样的方法来实现按需选取,复杂的地方是一个Join可能依赖另一个Join,就像例子里的FACILITY和SITE的那样。另外,Fields为空的时候必须转换为“*”,UsedJoins不为空时From部分用Join,UsedJoins为空时from部分则必须用UsedTables或SubQuerys来代替。

      暂时先到这,眼花脖子酸,下回给出对象图和实现思路。

posted @ 2008-04-23 09:39 陈鹏(偶是坏人) 阅读(1736) | 评论 (6)编辑

JSCFO当前版本号2.8。实现功能如下:
1、可同时对多个文件进行操作。
2、格式化代码。
3、合并文件。
4、变量名混淆(选择变量名混淆后会随机在if、for、while、dowhile中插入混淆用的判断)。
5、流程混淆。可选择打乱流程时进行分块的行数。(由于Javascript中没有goto,所以打乱代码后用for或while配合if或switch保证代码执行顺序。for或while和if或switch的配合有一定随机性,较难被找到规律。此功能会造成代码增大,不过没有办法)
6、字符串混淆。(目前方法较简单,后期考虑用压缩替代混淆)
7、数字混淆。(用数学表达式替代常数)
8、压缩。(简单的方法,Pack2的那种)
9、格式化和混淆时自动修正结尾没有加分号的情况。
10、修改正则表达式的写法,避免所有代码在同一行时发生错误。
界面如下:

试用版中ObfuscateOptions(混淆选项)不可设置,默认使用了变量混淆、流程混淆、字符串混淆、数字混淆;流程混淆时Lines(方块的行数)默认为1,压缩也不启用。Format Only(仅格式化)和Merge(合并)选项可自由设置。

使用方法:1、用File菜单下的Add和Remove菜单项增加删除文件。2、混淆选项可以不用选择,如果需要合并多个文件则钩选Merge,如果仅需要格式化代码请钩选Format Only。3、点击Obfuscate菜单下的Go进行混淆。如果Output Folder(输出目录)中有与源文件同名的文件,则在混淆后的文件名前加“Obfuscated_”。4、双击Files(文件列表)中的项可以在新窗口中显示所选择的JS文件格式化后的代码。

混淆前的代码:

var x = 0;
var z = false;

function Test()
{
    
for(var i=0; i<x; i++)
    {
        
if(z)
        {
            
var s = "Today's date is: ";
            
            
var d = new Date();
            s 
+= (d.getMonth() + 1+ "/";            //Get month
            s += d.getDate() + "/";                   //Get day
            s += d.getYear();

            alert(i
+ ":" + s);
        }
    }
}

= 10;
= true;
Test();

混淆后的代码(未压缩):

var $_A, x, z, $_E;
function Test()
{
    
for(var i = ((0x36 * 0x0+ 0x0); i < x; i++)
    {
        
if(z)
        {
            
var s, d, $_D;

            $_D 
= 0;
            
for(var $_D = 5; $_D >= 0; $_D--)
            {
                
if($_D == ((0x22 * 0x3- 0x66))
                {
                    alert((i 
+ $_A("%3B")) + s);
                }
                
if($_D == ((0x4f * 0x3- 0xec))
                {
                    s 
+= d.getYear();
                }
                
if($_D == ((0x3 * 0x3- 0x4))
                {
                    s 
= $_A("e%C3%D3%C5%DA%A0%9A%93%84%C5%D5%D9%85%89%DC%ADZ");
                }
                
if($_D == ((0x42 * 0x7- 0x1cc))
                {
                    s 
+= (d.getDate() + $_A("0"));
                }
                
if($_D == ((0x26 * 0x2- 0x49))
                {
                    s 
+= ((d.getMonth() + ((0x8 * 0x2- 0xf)) + $_A("0"));
                }
                
if($_D == ((0x1d * 0x7- 0xc7))
                {
                    d 
= new Date();
                }
            }
        }
    }
}
$_A 
= function ($_B)
{
    
var $_C;
    $_B 
= unescape($_B);
    $_C 
= String.fromCharCode($_B.charCodeAt(0- $_B.length);
    
for(var i = 1; i < $_B.length; i++)
    {
        $_C 
+= String.fromCharCode($_B.charCodeAt(i) - $_C.charCodeAt(i - 1));
    }
    
return $_C;
};

$_E 
= 5;
while($_E > 0)
{
    $_E
--;
    
if($_E == ((0x3a * 0x7- 0x193))
    {
        z 
= false;
    }
    
if($_E == ((0x30 * 0x0+ 0x1))
    {
        z 
= true;
    }
    
if($_E == ((0x0 * 0x2+ 0x0))
    {
        Test();
    }
    
if($_E == ((0x1e * 0x3- 0x56))
    {
        x 
= ((0x27 * 0x5- 0xc3);
    }
    
if($_E == ((0x26 * 0x2- 0x4a))
    {
        x 
= ((0x55 * 0x1- 0x4b);
    }
}

压缩后的代码如下:

eval(function(e,d){e=e.replace(new RegExp('\\b\\w+\\b','g'), function($0){return d[$0]});return e;}('0 $1=2($3){$3=4($3);0 $5=6.7($3.8(9)-$3.10);11(0 12=13;12<$3.10;12++){$5+= 6.7($3.8(12)-$5.8(12-13));}14 $5;};0 $15,$16;2 17(){11(0 12 = (18*19-20);(12 < $15) && !(21.$22>23);12++){24($16){0 $25,$26;$25 = $1(\"27%28%29%30%31%32%33%34%35%30%36%37%38%39%40%41\");$26 = 42 43();$25 += (($26.44() + (45*18-46)) + $1(\"9\"));$25 += ($26.47() + $1(\"9\"));$25 += $26.48();49(((12 + $1(\"%50\")) + $25));}}}$15 = (51*52-53);$16 = 54;$15=(55*18-56);$16=57;17();''var|_A|function|_B|unescape|_C|String|fromCharCode|charCodeAt|0|length|for|i|1|return|_D|_E|Test|0x2|0x4|0x8|this|_F|2|if|_G|_H|e|C3|D3|C5|DA|A0|9A|93|84|D5|D9|85|89|DC|ADZ|new|Date|getMonth|0x4b|0x95|getDate|getYear|alert|3B|0x52|0x3|0xf6|false|0x12|0x1a|true'.split('|')));

目前的流程混淆利用while或for配合if或switch控制打乱后代码的正常执行顺序,希望有朋友能提出更好的建议。字符串的加密也让人感到头疼。
混淆后的代码较原有代码增大不少,主要原因是针对每一行代码都进行了打乱并且没有进行压缩处理。进行压缩处理之后,还是可以接受的。

后期目标如下:
1、支持包含<Script language="javascript"></script>标记的文件。(暂定为此标记)
2、用压缩算法替代现在那个简单的字符串混淆方法。
3、尝试寻找新的流程混淆方法。
4、考虑是否加入函数名混淆。(个人目前认为函数名混淆的意义不大)
5、加入混淆排除选项。
6、考察是否有更高效的压缩算法,以替代Pack2目前的方法。

PS:
之前版本有用Themida加壳,发现很多朋友用ESET,故不用Themida,已更新。请想试用的朋友放心,绝无病毒。
至于有人说我不放源码的问题,这个似乎没有规定吧。我这个还只是给大家试用,并没有说要收钱干啥的。所以对源码有意见的请自己保留吧
准备开始新版本的工作,旧版本暂停下载。

posted @ 2008-04-23 03:20 陈鹏(偶是坏人) 阅读(1768) | 评论 (20)编辑
      最近正在参与一个我很不看好的项目,一次讨论时听了数遍:“……等到重构的时候……”,于是有感而发。希望对路过的人有所帮助。

    《重构——改善既有代码的设计》相信这本书大家都不陌生。“在不改变代码外在行为的前提下,对代码做出修改,以改进程序内部结构”、“在代码写好之后改进它的设计”也算是耳熟能详。重构不是坏事,但是却不要项目一开始就满嘴“没事,就这样吧,重构的时候我们再……”。真不知大师看到这种结果会如何感慨。

     重构能将糟糕的设计修订为一个良好的设计,但是千万不要忘了,重构也是有成本的。胡乱设计,天马行空之后,自信满满地说:“我们开始重构吧”。如何重构呢?1、老老实实的重新设计、重写代码;2、开始所谓的“重构”,在程序里贴满狗皮膏药。如果你选择的是1,为什么开始时不适当规划一下呢?当项目全部完成后,告诉你的程序员:“我们之前的设计有误,需要好好整理一下(推翻)”你的程序员会有什么感想?2、不管设计是否有失误,我们只管修改出错的代码和对代码进行优化。不断修改和优化的结果一定等价于重新设计。肯定有人说:“没错啊,通过重构,使我们的系统更加高效和稳定了”。这里并不是说喜欢重构有什么问题,只是不应完全让系统依赖重构来完成。

      个人认为项目开发的过程应该是:
         1、了解需求。
         2、进行适当的设计、规划。
         3、依照设计尽快完成系统原型,提交用户或测试部门测试。
         4、根据反馈,修改错误、完善系统。并对现有代码进行重构。
         5、重复4。直到项目完成。
         6、继续走我们的重构之路,使项目中的代码更加精炼、完善,并可为其他项目重用。

      有些人过度设计,喜欢自己臆测用户的需求、业务流程,美其名曰“从用户角度出发”,结果程序员花了大量的工作实现之后,提交给用户才发现无法满足需求。有些人就像上面说的,只讲究重构,却不进行设计、规划。还有些人总强调对象的语义、代码的结构,却忽略了系统的总体架构。每一个问题都可以经过重构,得到解决,但是成本呢?告诉用户:“请您稍等,我们正在进行重构,很快就会有一个健壮的系统提交给您使用了”。对于不懂行的用户,可以慢慢忽悠,但是忽悠一时不能忽悠一世,用户总有那么一天会说“这么简单的东西,那个谁谁谁找人做很快就搞定了。你们怎么这么久,是不是水平有问题啊?”。如果用户也是个内行人怎么办?立马露馅,丢了面子不说,还少了一个经济来源。

      “重构的每个步骤都很简单,甚至简单过了头,你只需要把某个值域(field)从一个class移到另一个class,把某些代码从一个函数(method)拉出来构成另一个函数,或者是在classhierarchy中把某些代码推上推下就行了。但是,这些小小的修改累积起来就可以根本改善设计质量。这和一般常见的‘软件会慢慢腐烂’的观点恰恰相反”我相信,聚沙成塔也必须有一个坚实的基础作为依托。项目初期良好的设计、规划不仅能节约成本,进行重构的时候也会更得心应手。
posted @ 2008-04-19 00:38 陈鹏(偶是坏人) 阅读(2126) | 评论 (21)编辑
      最近到哪里都能看到一大群的网友准备找家乐福的麻烦,顺便发表一下感想。
      个人认为这是一种不成熟、不理智的行为。
      就算法国支持某些对我们不利的组织或者个人或者其他,和中国的家乐福超市似乎没有什么直接的关系,毕竟超市没有打出相同的旗帜吧。组织网友去闹事,不论是什么做法,都如同现在网友们表示强烈愤慨的某些人的行为一样吗?如果真相表示一下自己的爱国热情,完全可以在各大超市的外面搞一个爱国主义宣传,将某些恶势力的丑行让更多的中国人了解;顺便捐款,资助一下失学儿童或者贫困山区。相信这个方法比网络上流传的那些5.1干啥干啥好得多吧。
      换而言之,动不动就口口声声抵制**货,何不放眼望望自己家里究竟有多少国货呢?其实人的心理很明显,只要物美价廉,谁都喜欢。不要一味强调抵制,多想想如何向大众宣传,如何学习别人的优势,如何诚信经营,将自己的每一件产品都用心做好。
      奉劝网友们不要冲动,不要因为自己的行为给我们祖国抹黑。嘴上挂着爱国,QQ、MSN的昵称都改成爱国又能怎么样呢?不理智的行为只能让更多恶势力有把柄来诬蔑我们和我们的祖国。
posted @ 2008-04-17 11:26 陈鹏(偶是坏人) 阅读(46) | 评论 (0)编辑
综合了几个国内、外JavaScript保护软件的功能后,对JavaScript代码保护有了一些想法。

共有如下8点:
1、字符串的压缩:单纯的混淆,如Escape后再进行某种变换,做法是不错,但是字符串的长度增加了不少,所以首选的还是一个简单的压缩算法来压缩。不过目前我实在想不出针对简单字符串有什么好的压缩方法,估计实现的时候还是用前面说的方法了。
2、数字的转换:这个似乎没有什么太好的方法,把单纯的10进制数转换成等价的算数表达式,虽然占用的字符多,但是有效果。
3、局部变量混淆:就是更改名字了。这个就是越短越好,没什么好说的。
4、全局变量和方法名:暂不考虑混淆。毕竟很多JavaScript库是给别人调用的。混淆了名称没有什么意义,而且就算设置排除或者指定之类的选项,个人认为麻烦了一些。
5、流程混淆:由于JavaScript没有goto,所以得用一些其他的手段来打乱代码的执行顺序,没有goto的话实现肯定会使代码增大,不过没办法,这才是最有效果的保护方式。流程混淆要做到一定的随机性,暂时也不算是有太好的方法。
6、压缩:用简单的压缩代码进行压缩,确保混淆后的代码不至于增大数倍。
7、合并:将多个文件合并到一个文件中。
8、格式化:可以什么混淆选项都不选,仅仅对文件进行格式化。

不知有什么错误和不足之处,希望路过的XDJM指点。

我现在的难点主要在“简单字符串压缩”,暂时没有找到什么比较好的方法,也请高手赐教。
posted @ 2008-04-13 23:43 陈鹏(偶是坏人) 阅读(351) | 评论 (4)编辑

尚次看了几个国产的,这次研究一下国外的。
上google搜了一下,找了几个JASOB、jsoSD-Obfuscator、Stunnix-JS-Obfus,由于这几个东西都要钱,所以不知道究竟是正式版就只有少数功能,还是试用版有所保留。

JASOB、jso、SD-Obfuscator的效果都差不多,去掉了多余的空格和换行,并混淆了变量。JASOB支持的文件似乎多些,对于html文档中的对象的id也可以做混淆。不过由于字符串、数字等都没有混淆,流程也没有打乱,个人感觉实际的效果似乎不大。

Stunnix-JS-Obfus倒是比较有趣,居然是个web的界面,而且还是大红色的背景,看得我眼花缭乱。选项贼多,不过我随便选了几个,没看出什么效果,这个东西最麻烦的是会自动搜索文件,列出来,那个慢啊。不过看它网站上的演示倒是比上面那几个有前途些,多了字符串混淆和数字混淆(将数字替换成一个表达式)。这东西还有一个基于Console的咚咚,看帮助,选项超多,什么都选项都不要,直接混淆了一个js,输出的居然是个嵌套了n层的类似packer2的东西,吐血啊。

posted @ 2008-04-12 21:59 陈鹏(偶是坏人) 阅读(178) | 评论 (1)编辑