重复代码片段查询方法和装置
技术领域
本发明涉及信息技术,尤其涉及一种重复代码片段查询方法和装置。
背景技术
在软件开发过程中,经常会将工作量较大的软件开发项目分割为工作量较小的子项目,并由不同的程序员分别对子项目编写程序代码。当两个不同的子项目均需要用到同一个程序段时,往往会出现两个不同的子项目中出现完全相同的程序代码,在这种情况下,若需要对该程序段进行修改,则需要对各子项目中重复出现的全部该程序段均进行修改,工作量较大且容易出现错误。
因此,在对子项目进行整合之后,需要对整合所获得的项目进行优化,具体来说,需要针对项目的程序代码进行重复代码片段的查询,将查询出的重复代码片段进行复用或者说合并,从而提高可维护性。
现有技术中在查询重复代码片段时,需要针对每一个代码片段依次与其余代码片段进行比较,从而判断出是否存在重复代码片段,由于这种方式需要将全部的代码片段数据存储在内存中,因此运行开销较大。
发明内容
本发明提供一种重复代码片段查询方法和装置,用于解决现有技术中查询是否存在重复代码片段时,运行开销较大的技术问题。
为达到上述目的,本发明的实施例采用如下技术方案:
第一方面,提供了一种重复代码片段查询方法,包括:
获取包含多个代码片段的代码序列的各后缀;
将各后缀所包含的代码片段在所述代码序列中的排序位置作为元素,构造所述代码序列的第一后缀数组;
根据由所述第一后缀数组中的各元素所得到的后缀,计算各后缀之间的公共部分;
利用所述公共部分,查询所述多个代码片段中的重复代码片段。
第二方面,提供了一种重复代码片段查询装置,包括:
后缀模块,用于获取包含多个代码片段的代码序列的各后缀;
构造模块,用于将各后缀所包含的代码片段在所述代码序列中的排序位置作为元素,构造所述代码序列的第一后缀数组;
计算模块,用于根据由所述第一后缀数组中的各元素所得到的后缀,计算各后缀之间的公共部分;
查询模块,用于利用所述公共部分,查询所述多个代码片段中的重复代码片段。
本发明实施例提供的重复代码片段查询方法和装置,通过对多个代码片段所构成的代码序列确定各后缀之后,将各后缀所包含的代码片段在代码序列中的排序位置作为元素,构造代码序列的后缀数组,从而根据由后缀数组中的各元素所计算出的各后缀之间的公共部分,查询所述多个代码片段中的重复代码片段。由于所构造的后缀数组中存储的仅为一个指示后缀中代码片段所在排序位置的数值,而不是将后缀中代码片段的内容存储在后缀数组中,因此,也就不必将全部的代码片段内容存储在内存中,从而运行的开销较小,解决了现有技术中查询是否存在重复代码片段时,运行开销较大的技术问题。
上述说明仅是本发明技术方案的概述,为了能够更清楚了解本发明的技术手段,而可依照说明书的内容予以实施,并且为了让本发明的上述和其它目的、特征和优点能够更明显易懂,以下特举本发明的具体实施方式。
附图说明
通过阅读下文优选实施方式的详细描述,各种其他的优点和益处对于本领域普通技术人员将变得清楚明了。附图仅用于示出优选实施方式的目的,而并不认为是对本发明的限制。而且在整个附图中,用相同的参考符号表示相同的部件。在附图中:
图1为本发明实施例一提供的一种重复代码片段查询方法的流程示意图;
图2为重复代码片段查询方法的执行示意图;
图3为本发明实施例二提供的一种重复代码片段查询方法的流程示意图;
图4为逐个源文件进行处理的流程示意图;
图5为逐行对源文件的代码进行处理的流程示意图;
图6为高度数组的计算示意图;
图7为本发明实施例三提供的一种重复代码片段查询装置的结构示意图;
图8为本发明实施例四提供的一种重复代码片段查询装置的结构示意图。
具体实施方式
下面将参照附图更详细地描述本公开的示例性实施例。虽然附图中显示了本公开的示例性实施例,然而应当理解,可以以各种形式实现本公开而不应被这里阐述的实施例所限制。相反,提供这些实施例是为了能够更透彻地理解本公开,并且能够将本公开的范围完整的传达给本领域的技术人员。
为了便于理解本发明所提供的重复代码片段查询方法和装置,在描述具体实施例之前,对实施例中所涉及的技术术语进行解释:
序列(Sequence):元素的有序排列。在本发明实施例中,将代码片段作为元素,对这些代码片段进行有序排列所获得的序列称为代码序列。
后缀(Suffix):后缀是指从某一个位置i开始到整个序列结尾的一个子序列,也叫子串或余串,记做Suffix(i)。
后缀数组:后缀数组是一个一维数组,它用于记录后缀的有序排列。在本发明实施例中,第一后缀数组中的元素为后缀中所包含的代码片段在代码序列中的排序位置,第二后缀数组中的元素为后缀的内容。
名次数组:名次数组Rank[i]保存的是Suffix(i)在所有后缀中从小到大排列的“名次”。
高度数组:定义高度数组中的元素height[i]=Suffix(sa[i-1])和Suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀。那么对于j和k,不妨设rank[j]<rank[k],则有以下性质:Suffix(j)和Suffix(k)的最长公共前缀为height[rank[j]+1],height[rank[j]+2],height[rank[j]+3],…,height[rank[k]]中的最小值。
最长公共前缀:最长公共前缀是指从两个后缀的第一个元素开始,所能够获得的两后缀之间具有的最长相同部分,这里的最长相同部分也称为公共部分,这个最长相同部分所包含的元素数称为最长公共前缀的长度。例如两个后缀分别为“accs”和“acbsa”则两个后缀的最长公共前缀为“ac”,长度为2。
停用词:可以明确表示整行为非有效代码的词,如“#include”,“import”。根据语言的不同,停用词会各不相同。
此处简要解释了技术术语的含义,以上技术术语会在后续结合具体实施例进行进一步地解释。
下面结合附图对本发明实施例提供的重复代码片段查询方法和装置进行详细描述。
实施例一
在程序开发过程中,程序员从网络或者其他位置复制了同一段代码并将其粘贴到当前所开发的程序中的不同位置时,便会在多个源文件中的不同位置中出现重复代码片段。
由于重复代码片段极易导致程序的维护性差,因此,作为程序质量评价的指标之一,需要对源文件中各代码片段所构成的代码序列中是否存在重复代码片段进行查询。作为一种可能的实现方式,本实施例所提供的方法可以由代码质量评价工具执行。图1为本发明实施例一提供的一种重复代码片段查询方法的流程示意图,如图1所示,方法包括:
步骤101、获取包含多个代码片段的代码序列的各后缀。
其中,源文件中的代码被预先划分为各个代码片段,每一个代码片段包括预设行的程序代码,这里的预设行数可以根据查询精度进行确定,若需要较高的查询精度,可以设置较低的预设行数,若需要较低的查询精度,可以设置较高的预设行数,但预设行数的取值至少为一。在不同次的查询中,各次查询所划分的代码片段之间可以具有相同的行数,也可以具有不同的行数。为了便于代码片段的划分,每一次查询中,各代码片段之间可以具有相同的行数。
作为一种可能的实现方式,根据各个代码片段在源文件中所在行的前后位置,按照预设顺序,例如由前至后的顺序或者由后至前的顺序,对代码片段进行排序,获得代码序列,在代码序列中,每一个元素对应源文件中的一个代码片段。进而,将代码序列中每一个代码片段依次作为起始元素,选取从该起始元素到代码序列中的最后一个元素为止的一段子序列,作为该代码片段所对应的后缀,也就是后缀的总个数与代码序列中元素的个数相同。
步骤102、将各后缀所包含的代码片段在代码序列中的排序位置作为元素,构造代码序列的第一后缀数组。
具体的,对代码序列的各后缀按照字典序进行排序,按照各后缀的排序,将各后缀中的代码片段在代码序列中的排序位置依次作为第一后缀数组中对应元素的取值。由于第一后缀数组中存储的仅为一个指示后缀中代码片段所在排序位置的数值,而不是将后缀中代码片段的内容存储在后缀数组中,因此,也就不必将全部的代码片段内容存储在内存中,从而运行的开销较小,解决了现有技术中查询源文件中是否存在重复代码片段时,运行开销较大的技术问题。
步骤103、根据由所述第一后缀数组中的各元素所得到的后缀,计算各后缀之间的公共部分。
具体的,首先,从代码序列中,读取第一后缀数组中元素所指示的排序位置得到后缀,根据得到的后缀所对应代入得到的第二后缀数组,计算得到代码序列的高度数组,其中高度数组中的元素用于指示各后缀之间的最长公共前缀。具体来说,从代码序列中,读取到第一后缀数组中元素所指示的排序位置得到后缀之后,将得到的后缀对应记录到第一后缀数组中从而代入第一后缀数组得到第二后缀数组。根据第二后缀数组构造出名次数组,该名次数组和第二后缀数组为对偶关系。进而采用倍增算法对第二后缀数组和名次数组进行计算,获得高度数组。高度数组中的元素为第二后缀数组中所指示的相邻两后缀的最长公共前缀的长度,这里的最长公共前缀便是相邻两后缀的公共部分,且该公共部分出现的位置为两后缀的起始位置。这里提及的最长公共前缀的长度便是相邻两后缀的公共部分中所包含的代码片段的个数。
由于第一后缀数组中的元素仅为指示后缀中代码片段在代码序列中所在位置的数值,而不是将后缀中代码片段的内容存储在第一后缀数组中,因此,在计算高度数组时,若需要读取后缀中代码片段的内容可根据第一后缀数组中的元素,从磁盘或者非内存的存储位置读取代码序列中对应的排序位置。由于在计算高度数组时不是对全部的后缀均进行读取,而是仅对需要计算重复部分的后缀进行读取,并且在计算出公共部分后还可以释放所读取的后缀,因此,节省了内存空间。
步骤104、利用公共部分,查询多个代码片段中的重复代码片段。
具体的,在上一步骤中所计算出的高度数组中,每一个高度数组元素对应指示第一后缀数组中相邻的两个元素指示的后缀的公共部分,高度数组元素的具体取值指示了公共部分中所包含的代码片段的个数。
因此,在查询重复代码片段时,若高度数组中存在非零元素,则说明至少两个后缀之间存在公共部分,据此可以判定存在重复代码片段。
进而还可以根据高度数组中的非零元素的取值,确定该非零元素所指示的公共部分在两个后缀中的相对位置,以及公共部分所包含的代码片段的个数。具体的,当高度数组中的非零元素的取值为q时,q为自然数,相对位置即为从后缀的首个代码片段开始的q个代码片段。例如:非零元素的取值为3,则相对位置为后缀的第一个代码片段至第三个代码片段。通过结合第一后缀数组中对应的这两个后缀的元素的取值,可以确定两个后缀在代码序列中的排序位置。最终,根据两个后缀在代码序列中的排序位置,和公共部分在两个后缀中的相对位置,确定该公共部分对应的重复代码片段在所述代码序列中的位置。
需要说明的是,这里的需要计算是否存在公共部分的两个后缀可以是相邻的,也可以是不相邻的。若为相邻的情况,则仅需查看高度数组中的对应的一个元素的取值,该元素的取值即为相邻两个后缀之间的公共部分所具有的代码片段的个数。若为不相邻的情况,则需要查看后缀之间任两相邻的后缀对应的高度数组中的元素取值,从中选择最小值,将其作为不相邻的这两个后缀之间的公共部分所具有的代码片段的个数,公共部分的相对位置为从首个代码片段开始的符合该个数的代码片段。
进一步,还可以对查询出的重复代码片段进行修正,这是由于尽管已查询出了重复代码片段,但由于本实施例中该重复代码片段是用于进行代码质量评价的,一般来说同一源文件中的重复代码片段是有其特殊意义的,从而若在同一源文件中的两个不同位置出现了重复代码片段,则进行忽略,不计入代码质量评价结果中。
本实施例所提供的方法,如图2所示,通过对多个代码片段所构成的代码序列确定各后缀之后,将各后缀所包含的代码片段在代码序列中的排序位置作为元素,构造代码序列的后缀数组,从而根据由后缀数组中的各元素所计算出的各后缀之间的公共部分,查询所述多个代码片段中的重复代码片段。由于所构造的后缀数组中存储的仅为一个指示后缀中代码片段所在排序位置的数值,而不是将后缀中代码片段的内容存储在后缀数组中,因此,也就不必将全部的代码片段内容存储在内存中,从而运行的开销较小,解决了现有技术中查询是否存在重复代码片段时,运行开销较大的技术问题。
实施例二
为了清楚说明上一实施例,本实施例提供了一种重复代码片段查询方法的具体执行流程,用于实现高精度查询,本实施例中所提及的后缀数组SA对应第一后缀数组,本实施例中所提及的后缀数组SA’对应第二后缀数组。图3为本发明实施例二提供的一种重复代码片段查询方法的流程示意图,一般情况下,一个项目由于内容较多,该项目的代码通常分别由多个源文件进行存储,在这一应用场景下,本实施例中,为了提高查询精度,将一行代码作为一个代码片段进行重复代码片段查询,如图3所示,包括:
步骤201、对各个源文件中的代码进行预处理。
具体的,预处理过程可以依据预设规则进行,预设规则包括三个子规则:
A、针对每一行代码,删除以停用词为开头的代码所在行。
B、针对每一行代码,删除预设字符;预设字符包括空格和制表符。
C、在每一源文件的结尾增加源文件对应的分隔行,分隔行为该源文件的唯一标识或该文件的存储路径。
设置子规则A是由于:停用词为开头的代码一般用于进行标记和解释,该代码没有实际意义,因此在子规则A中删除以停用词为开头的代码所在行。
设置子规则B是由于:空格和制表符不仅没有实际意义,同时空格的数量和制表符的位置还容易对代码是否重复的判断造成干扰,为了提高重复代码片段查询的准确度,在子规则B中将这些容易造成干扰的预设字符去除。
设置子规则C是由于:由于不同源文件作为相对独立的代码,因此,这里所说的重复代码片段不能跨文件,也就是说重复代码片段中的各行代码均应当在同一个源文件中。若一个源文件的结尾部分代码和下一个源文件的开头部分代码构成一个代码片段,该代码片段即使与第三个源文件中部分代码重复,这里不将其认作重复代码片段。为了避免将其认作重复代码片段的情况出现,子规则C中在两源文件之间插入不重复的特殊行,即分隔行,从而进一步提高了重复代码片段查询的准确性。
步骤202、将预处理所获得的每一行代码作为一个元素,生成代码序列Sequence。
具体的,将全部源文件中的代码进行排序,获得代码序列。还可以生成一个待处理串文件,用于记录代码序列中每一个元素对应的源文件、行号和行内容,其中,行内容便是代码序列中的元素的取值,行号对应该元素对应的代码在源文件中所在行。从而可以在代码序列中查询到重复代码时,反查重复代码在源文件中的位置。
为了清楚说明具体执行过程,本实施例提供了步骤201和202具体的执行流程说明,图4为逐个源文件进行处理的流程示意图,如图4所示,当确定存在下一个待处理源文件时,在当前源文件结尾插入对应的分隔行,进而逐行对当前源文件的代码进行处理,并将处理结果记录到待处理串文件中。具体针对每一行代码的处理过程参考图5,图5为逐行对源文件的代码进行处理的流程示意图,如图5所示,首先判断当前行的代码是否以停用词开头,若是,则不再对该行进行处理,反之则进行清除空格和制表符的处理,并在清楚之后判断处理后的当前行的代码是否为空字符串,若否,则将当前行的代码的内容记录到待处理串文件中,并在待处理串文件中所记录的当前行的对应位置添加文件名和当前行的行号等信息。从而待处理串文件中的每一条记录应当至少包含有行号、行内容和文件名。
进而针对所最终获得的待处理串文件中的行内容按照行号和文件名排列,获得代码序列Sequence。
步骤203、构建代码序列Sequence的后缀数组SA。
代码序列Sequence的后缀Suffix包括了至少一行代码,具体来说包含了从Sequence中的某一个元素开始到最后一个元素。按照预设字典序,将Sequence的各个后缀按照字典序进行排序之后,确定每一个排好序的后缀中的代码在Sequence中的起始排序位置,并将排序位置作为后缀数组SA中的元素记录到后缀数组SA中。
例如:针对如下代码序列Sequence构建后缀数组SA,其中,下表第一列为元素在Sequence中的排序,下表第二列为元素在Sequence中的取值。
表1Sequence示意表
为了便于描述,将Sequence中的每一个元素记为一个字母:
a=“A(ind)=B(ind);”
b=“B(ind)=tmp;”
获得如下代码序列Sequence{a,a,b,a,a,a,a,b}。
代码序列Sequence的各后缀及其对应的起始行号分别为:
1 |
a,a,b,a,a,a,a,b |
2 |
a,b,a,a,a,a,b |
3 |
b,a,a,a,a,b |
4 |
a,a,a,a,b |
5 |
a,a,a,b |
6 |
a,a,b |
7 |
a,b |
8 |
b |
表2Sequence的各后缀及其对应的起始行号
按照字典序,对表2中各后缀进行排序,并根据后缀的排序对表2按照行进行重排,具体来说,排序越靠前的后缀,在下表中该后缀所在行也靠前。
需要说明的是,字典序中规定了字符之间的相互顺序,这里的字符包括字母、符号和数字等,例如:根据ASCII(American Standard Code for Information Interchange,美国标准信息交换代码)中,各字符的二进制编码的顺序作为字符之间的相互顺序。
表3重排后的Sequence的各后缀
表3中的第一列便是后缀数组SA中的元素的取值,第一列中行号的排列顺序便是后缀数组SA中的元素的排序,所构造出的后缀数组SA={4,5,6,1,7,2,8,3}。
步骤204、根据后缀数组SA计算高度数组HA。
从代码序列Sequence中,读取后缀数组SA中元素所指示的排序位置得到后缀,根据得到的后缀所对应代入的后缀数组SA’,计算得到高度数组HA,高度数组HA中的元素用于指示后缀数组SA’中对应的相邻两个后缀的最长公共前缀(Longest Common Prefix)。
对两个后缀u和v从首个字符开始顺次比较u和v的对应字符是否相同,若相同则继续比较直到确定出对应字符持续相等的最终位置,将首个字符至这个最终位置称为这两个后缀的最长公共前缀。若首个字符便为不同字符,则两个后缀的最长公共前缀为0。
图6为高度数组的计算示意图,如图6所示,横线上方为代码序列,横线下方为该代码序列的各后缀,各后缀的排序是按照所对应的后缀数组SA’中各元素顺序进行排列的。左侧为相邻两个后缀的最长公共前缀的长度,也就是高度数组HA中各元素的取值。
例如:第一行后缀{a,a,a,a,b}第二行后缀{a,a,a,b}的最长公共前缀为{a,a,a},包含有3个元素,从而最长公共前缀的长度为3。
又例如:第六行后缀{a,b,a,a,a,a,b}第二行后缀{b}的最长公共前缀为空集,包含0有个元素,从而最长公共前缀的长度为0。
相似的对其余相邻后缀计算最长公共前缀的长度,得到HA={3,2,3,1,2,0,1}。
步骤205、根据高度数组HA映射回代码序列确定是否存在重复代码片段。
已知HA={3,2,3,1,2,0,1},SA={4,5,6,1,7,2,8,3}。以HA[1]举例,HA[1]表示SA[1]与SA[2]指示的排序位置对应的后缀具有最长公共前缀,最长公共前缀长度3。SA[1]=4,SA[2]=5,即表示从代码序列中第4个元素开始的后缀和从代码序列中第5个元素这个位置开始的后缀具有长度为3的重复行。就是说代码序列中的两个范围,即第4个元素至第6个元素,以及第5个元素至第7个元素,具有重复代码,可以记为[4,6]=[5,7]。
根据高度数组不仅可以计算相邻两后缀之间的最长公共前缀,还可以计算任两后缀之间的最长公共前缀,比如SA[2]与SA[6],则根据高度数组HA确定SA[2]至SA[6]中相邻两后缀之间的最长公共前缀的长度依次为2,3,1和2,从中取最小值1作为SA[2]与SA[6]的最长公共前缀的长度,即代码序列中第2个元素和第6个元素重复[2]=[6]。
同理,可以得到更多的具有重复行的组合,例如:
SA[2],SA[3]有长度2的重复[5,6]=[6,7];
SA[3],SA[4]有长度3的重复[6,8]=[1,3];
......
SA[7],SA[8]有长度1的重复[8,8]=[3,3]。
其中,有一些具有重复的范围是重叠的,如[4,6]=[5,7],其中第5至6个元素是重叠的,可以抛弃掉。剩下的都是有效的重复元素。例如:[6,8]=[1,3]所指示的代码序列的第6至8个元素中的代码片段与第1至3个元素中的代码片段重复。
步骤206、根据重复代码片段在代码序列中的排序位置,确定重复代码片段在源文件中的位置。
具体的,在确定代码序列中存在重复代码片段之后,可以根据待处理串文件,确定代码序列中的该重复代码片段在源文件中的位置,包括源文件的文件名和行号。
实施例三
图7为本发明实施例三提供的一种重复代码片段查询装置的结构示意图,如图7所示,包括:计算模块31、后缀模块32、构造模块33和查询模块35。
后缀模块32,用于获取包含多个代码片段的代码序列的各后缀。
构造模块33,用于将各后缀所包含的代码片段在所述代码序列中的排序位置作为元素,构造所述代码序列的第一后缀数组。
计算模块31,用于根据由所述第一后缀数组中的各元素所得到的后缀,计算各后缀之间的公共部分。
查询模块35,用于利用公共部分,查询多个代码片段中的重复代码片段。
实施例四
图8为本发明实施例四提供的一种重复代码片段查询装置的结构示意图,在图7的基础上,计算模块31进一步包括:读取单元311和计算单元312。
读取单元311,用于从所述代码序列中,读取第一后缀数组中元素所指示的排序位置得到后缀。
计算单元312,用于根据得到的后缀对应代入第一后缀数组所得到的第二后缀数组,计算得到各后缀之间的最长公共前缀。
查询模块35进一步包括:判定单元351、位置单元353和确定单元354。
判定单元351,用于当至少两个后缀之间存在公共部分时,确定所述多个代码片段中存在重复代码片段。
位置单元353,用于根据两个后缀所具有的公共部分在后缀中的相对位置,以及根据所述后缀所包含的代码片段在所述代码序列中的排序位置,确定所述公共部分对应的重复代码片段在所述代码序列中的排序位置。
确定单元354,用于根据最长公共前缀的长度,确定所述公共部分在所述两个后缀中的相对位置。还用于根据所述后缀数组中对应所述两个后缀的元素的取值,确定所述两个后缀所包含的代码片段在所述代码序列中的排序位置。
进一步,代码序列是根据各代码片段在源文件中的位置,对各代码片段进行排序得到的,基于此,重复代码片段查询装置还包括:定位模块34。
定位模块34,用于根据所述重复代码片段在所述代码序列中的排序位置,确定所述重复代码片段在所述源文件中的位置。
进一步,构造模块33,包括:排序单元331和取值单元332。
排序单元331,用于对所述代码序列的各后缀按照字典序进行排序。
取值单元332,用于将排序后的各后缀所包含的第一个代码片段在所述代码序列中的排序位置作为所述后缀数组中对应元素的取值。
进一步,该装置还包括:预处理模块36。
预处理模块36,用于根据预设规则对代码片段中的各行代码进行预处理。
具体的,预设规则包括:
针对每一行代码,删除以停用词为开头的代码所在行;
和/或,针对每一行代码,删除预设字符;
和/或,若所述代码片段分别属于至少两个源文件,在每一个源文件的结尾增加所述源文件对应的分隔行。
其中,分隔行包括所述源文件的唯一标识和/或所述文件的存储路径;所述预设字符包括空格和/或制表符。
本领域普通技术人员可以理解:实现上述各方法实施例的全部或部分步骤可以通过程序指令相关的硬件来完成。前述的程序可以存储于一计算机可读取存储介质中。该程序在执行时,执行包括上述各方法实施例的步骤;而前述的存储介质包括:ROM、RAM、磁碟或者光盘等各种可以存储程序代码的介质。
最后应说明的是:以上各实施例仅用以说明本发明的技术方案,而非对其限制;尽管参照前述各实施例对本发明进行了详细的说明,本领域的普通技术人员应当理解:其依然可以对前述各实施例所记载的技术方案进行修改,或者对其中部分或者全部技术特征进行等同替换;而这些修改或者替换,并不使相应技术方案的本质脱离本发明各实施例技术方案的范围。