Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java service analyser #3

Merged
merged 3 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
package cc.unitmesh.quality.extension

import cc.unitmesh.quality.QualityAnalyser
import cc.unitmesh.quality.extension.rule.TooManyRepositoryDependenciesRule
import chapi.domain.core.CodeDataStruct
import org.archguard.linter.rule.webapi.WebApiRuleSetProvider
import org.archguard.rule.core.Issue
import org.archguard.rule.core.IssuePosition
import org.archguard.rule.core.Rule
import org.archguard.rule.core.RuleType

class JavaServiceAnalyser(thresholds: Map<String, Int> = mapOf()) : QualityAnalyser {
private val webApiRuleSetProvider = WebApiRuleSetProvider()

override fun analysis(nodes: List<CodeDataStruct>): List<Issue> {
val serviceNodes = nodes.filter { it.filterAnnotations("Service").isNotEmpty() }
if (serviceNodes.isNotEmpty()) {
val results: MutableList<Issue> = mutableListOf()
TooManyRepositoryDependenciesRule().visitRoot(serviceNodes, fun(rule: Rule, position: IssuePosition) {
results += Issue(
position,
ruleId = rule.key,
name = rule.name,
detail = rule.description,
ruleType = RuleType.SERVICE_SMELL
)
})
return results;
}
//todo rule3: 调用的repository之间的关联度 -- ER关系上,关联度越强越好
//todo rule4: 不依赖controller层相关概念,如:request/response -- 层与层之间依赖关系清晰
//todo rule5: public filed数量 -- service应该都是private filed
//pending rule6: 不被外界访问的public method -- 应归属到bad smell
//pending rule1: service 长度小于x00行 -- bad smell large class

// 检查 Service 长度,调用的 repository 数量,
return listOf()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cc.unitmesh.quality.extension.rule

import chapi.domain.core.CodeDataStruct
import org.archguard.rule.core.IssueEmit
import org.archguard.rule.core.Rule

open class ServiceRule : Rule() {
open fun visitRoot(rootNode: List<CodeDataStruct>, callback: IssueEmit) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cc.unitmesh.quality.extension.rule

import chapi.domain.core.CodeDataStruct
import org.archguard.rule.core.IssueEmit
import org.archguard.rule.core.IssuePosition
import org.archguard.rule.core.Severity

/**
* Service should not dependent more than 5 repositories.
*/
const val LIMIT = 5

class TooManyRepositoryDependenciesRule : ServiceRule() {
init {
this.id = "too-many-repository-dependencies"
this.name = "TooManyRepositoryDependencies"
this.key = this.javaClass.name
this.severity = Severity.WARN
this.description = "Service should not dependent more than 5 repositories."
}

override fun visitRoot(rootNodes: List<CodeDataStruct>, callback: IssueEmit) {
rootNodes.forEach {
val repositoryCount = it.Fields.filter { it.TypeType.contains("Repository", true) }.count()
if (repositoryCount > LIMIT) {
callback(this, IssuePosition())
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package cc.unitmesh.quality;

import cc.unitmesh.quality.extension.JavaServiceAnalyser
import chapi.ast.javaast.JavaAnalyser
import chapi.domain.core.CodeDataStruct
import kotlinx.serialization.json.Json
import org.archguard.rule.core.RuleType
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import java.io.File
import java.nio.file.Paths
import kotlin.test.assertEquals

class JavaServiceAnalyserTest {
private fun loadNodes(source: String): List<CodeDataStruct> {
return Json { ignoreUnknownKeys = true }.decodeFromString(
File(this.javaClass.classLoader.getResource(source)!!.file).readText()
)
}

@Test
fun `should return empty list of issues when node is not a service`() {
val nodes = loadNodes("java/structs_HelloController.json")
val issues = JavaServiceAnalyser().analysis(nodes)

assertEquals(0, issues.size)
}

@Test
fun `should identify too many repository dependencies`() {
val path = getAbsolutePath("java/ServiceWithSixRepositories.java")
val data = JavaAnalyser().analysis(File(path).readText(), "ServiceWithSixRepositories.java").DataStructures
val issues = JavaServiceAnalyser().analysis(data)

Assertions.assertEquals(1, issues.size)
Assertions.assertEquals("TooManyRepositoryDependencies", issues[0].name)
Assertions.assertEquals("Service should not dependent more than 5 repositories.", issues[0].detail)
Assertions.assertEquals(RuleType.SERVICE_SMELL, issues[0].ruleType)
}

private fun getAbsolutePath(path: String): String {
val resource = this.javaClass.classLoader.getResource(path)
return Paths.get(resource!!.toURI()).toFile().absolutePath
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.afs.restapi.service;

import com.afs.restapi.repository.CompanyRepository;
import com.afs.restapi.repository.EmployeeRepository;
import com.afs.restapi.repository.DepartmentRepository;
import com.afs.restapi.repository.TeamRepository;
import com.afs.restapi.repository.GroupRepository;
import com.afs.restapi.repository.CommunitRepository;
import org.springframework.stereotype.Service;

@Service
public class Example {
private CompanyRepository companyRepository;
private EmployeeRepository employeeRepository;
private DepartmentRepository departmentRepository;
private TeamRepository teamRepository;
private GroupRepository groupRepository;
private CommunitRepository communitRepository;

public Example(CompanyRepository companyRepository, EmployeeRepository employeeRepository,
DepartmentRepository departmentRepository, TeamRepository teamRepository,
GroupRepository groupRepository, CommunitRepository communitRepository) {
this.companyRepository = companyRepository;
this.employeeRepository = employeeRepository;
this.departmentRepository = departmentRepository;
this.teamRepository = teamRepository;
this.groupRepository = groupRepository;
this.communitRepository = communitRepository;
}
}