삽질의 현장/- Opensource

SonarQube 프로젝트를 까보자

shovelman 2018. 12. 7. 23:21


최근 들어 SonarQube를 사용할 일이 많아져 리서치도 해보고 직접 사용해 보는 시간이 많았다.

사용해보며 좋은 솔루션이라는 생각과 함께 내부 구조가 궁금해지기 시작했다.

'좋은 구조를 파악해보고 이해한다면 언젠가 내 것으로 받아들일 수 있지 않을까?'

그리하여 SonarQube라는 오픈소스의 내부를 까보는 시간을 가져봤다. ('파헤쳐보다', '분석하다' 등 좋은 단어들이 있지만, '까보다'가 더 끌린다.)


(참고)

SonarQube에는 간략하게 '소스 정적 분석 도구' 라고 설명할 수 있을 것 같다.

컨벤션 위배(코드 스멜) / 버그/ 취약점 노출 등 여러 분석을 통해 소스 품질을 높이기 위해 참고할 수 있는 분석 결과를 제공하는 솔루션이다.

자세한 설명은 소스 정적 분석 도구 SonarQube 리서칭 를 참고하면 좋을 것 같다.

SonarQube 까보기

위에서 언급했던 것과 같이 SonarQube는 오픈소스이다.

소스를 살펴보기 위해 https://github.com/SonarSource/sonarqube.git 에서 코드를 받자.


저장소에 있는 디렉토리를 눌러보다가 Java 확장자가 보여 서버단 부터 까보기로 했다.

하지만... Java Application의 시작점부터 까볼 수 있으면 좋으련만...  도통 어디서 뭘 봐야 할지 감이 잡히지 않는다.


그래서 눈에 띄는 View단 으로 눈을 돌렸다. 눈앞에 먼저 띈 대시보드를 타켓으로 선정하기로 했다.

어떻게 들어가볼까? 결국 View는 전달 받은 데이터를 뿌려줄 테니... 어디서 데이터를 요청하는지를 살펴보자.


(https://sonarcloud.io/dashboard?id=sonarlint-visualstudio)



뭐든 프론트 단에서 진입점은 Index가 아닐까!? Index로 시작하는 파일을 검색해보자!


(있다 ㅋ)


흥미롭게도 프론트 단은 TypeScript와 React 조합으로 구현되어있다.

React는 최근 관심을 두고 조금씩 살펴보고 있어서, 큰 프로젝트에서 어떻게 사용되고 있는지 알 수 있을 것 같아 스스로 뿌듯함을 느끼며.... (프로젝트 잘 선정했어!)


아무튼... 

index.ts 파일을 훑어보며 startReactApp 이라는 이름이 눈에 띈다.



Promise.all([loadMessages(), loadUser(), loadAppState(), loadApp()]).then(
([lang, user, appState, startReactApp]) => {
startReactApp(lang, user, appState);
},
error => {
if (isResponse(error) && error.status === 401) {
redirectToLogin();
} else {
logError(error);
}
}
);

(소스보기)


안으로 들어가 보니 <Route path="/dashboard/index/:key ...> 코드가 보이는데, 아까 찾고자 했던 대시보드 페이지의 URL 링크랑 비슷하다.

어떻게 라우팅 처리를 하여 View 처리를 하고 있을까?



<Route
path="/dashboard/index/:key"
onEnter={(nextState, replace) => {
replace({ pathname: '/dashboard', query: { id: nextState.params.key } });
}}
/>

(소스보기)



우선은 계속 startReactApp 파일을 살펴보자.

대시보드를 타겟으로 잡았으니 Dashboard 키워드로 파일 내 검색을 해보면 뭔가 얻을 수 있지 않을까?

(얻었다.) Overview? 어디서 많이 봤다. 그렇다. 대시보드 하위 컬럼 중 하나가 Overview였다.



(https://sonarcloud.io/dashboard?id=sonarlint-visualstudio)


Dashboard 로 접근하면 Overview 항목이 제일 처음 나오니 childRoutes로 overviewRoutes가 명시되어있는게 일리가 있어 보이니 들어가 보자.



<RouteWithChildRoutes path="dashboard" childRoutes={overviewRoutes} />

(소스보기)



const routes = [
{
indexRoute: { component: lazyLoad(() => import('./components/App')) }
}
];

(소스보기)


App 파일을 import하고 있다. 코드를 쭉 살펴보다가 OverviewApp 이라는 컴포넌트가 보인다. 들어가 보자.



<OverviewApp
branchLike={branchLike}
component={component}
onComponentChange={this.props.onComponentChange}
/>

(소스보기)


OverviewApp은 타겟으로 잡은 대시보드의 프론트 단 담당 파일이었다!

혹시 몰라 div 클래스 명과 비교해보니 정확하다.


(https://sonarcloud.io/dashboard?id=sonarlint-visualstudio)



그렇다면, 아래의 영역들을 뿌려주는 데이터들은 어디서 나오는 것일까?


(https://sonarcloud.io/dashboard?id=sonarlint-visualstudio)



아래의 코드와 같이 데이터를 넣어주고 있다.



<div className="overview-domains-list">
<BugsAndVulnerabilities {...domainProps} />
<CodeSmells {...domainProps} />
<Coverage {...domainProps} />
<Duplications {...domainProps} />
</div>

(소스보기)


우리가 개발을 해보면 주로 변수 셋팅은 상단에 해주고 있지 않은가?

위로 올라가서 단서가 될 만한 메서드를 찾아보자.


loadHistory 라는 키워드가 뭔가 내 마음에 와닿는다. 들어가 보자.



componentDidMount() {
this.mounted = true;
this.props.fetchMetrics();
this.loadMeasures().then(this.loadHistory, () => {});
}

(소스보기)


사용되는 메서드들이 많은데 그중에 getAllTimeMachineData 메서드를 들어가 보자.



const metrics = uniq(HISTORY_METRICS_LIST.concat(graphMetrics));
return getAllTimeMachineData({
...getBranchLikeQuery(branchLike),
component: component.key,
metrics: metrics.join()
}).then(r => {

(소스보기)


getAllTimeMachineData 메서드는 결국 getTimeMachineData 메서드를 호출하게 되는데, 눈에 띄는 호출이 하나 보인다.



export function getTimeMachineData(
data: {
component: string;
from?: string;
metrics: string;
p?: number;
ps?: number;
to?: string;
} & BranchParameters
): Promise<TimeMachineResponse> {
return getJSON('/api/measures/search_history', data).catch(throwGlobalError);
}

(소스보기)


그렇다. JSON 데이터를 받아오고 있다.

저 '/api/measures/search_history'는 분명 컨트롤러 영역으로부터 데이터를 요청할 것이다!


자... 어디 한번 '/api/measures/search_history' 로 검색해보자.

아래와 같은 파일들이 검색된다. Java 파일 위주로 살펴보자.




Java 서버 단에서도 분명 라우팅을 통해 해당 메서드로 전달을 해주면, 해당 메서드에서 데이터를 쿼리하여 반환해줄 것이다.



/**
*
* This is part of the internal API.
* This is a GET request.
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/measures/search_history">Further information
* about this action online (including a response example)</a>
* @since 6.3
*/
public SearchHistoryResponse searchHistory(SearchHistoryRequest request) {
return call(
new GetRequest(path("search_history"))
.setParam("branch", request.getBranch())
.setParam("component", request.getComponent())
.setParam("from", request.getFrom())
.setParam("metrics", request.getMetrics() == null
? null : request.getMetrics().stream().collect(Collectors.joining(",")))
.setParam("p", request.getP())
.setParam("ps", request.getPs())
.setParam("pullRequest", request.getPullRequest())
.setParam("to", request.getTo()),
SearchHistoryResponse.parser());
}

(소스보기)



주석에 있는 링크를 한번 들어가 보자.



(https://next.sonarqube.com/sonarqube/web_api/api/measures/search_history)


내부적으로 호출만 가능한 것이 아니라, 외부에서도 API를 호출하면 해당 메서드를 통해서 값을 전달받을 수 있다.

후기

  • 단순 예제를 따라치며 React를 경험해보다가, 나름의 규모가 있는 프로젝트에 있는 React를 보니 SonarQube 프로젝트가 멘토로 느껴졌다.
    • 마치 신입 개발자(지금도 신입)가 이름 있는 초고수 개발자를 본다는 느낌이랄까?
  • 대충 훑어봤다는 느낌의 아쉬움과 원하는 목표를 달성했다는 성취감이 동시에 들었다.
    • 시간도 없고, 아는 것도 별로 없어 세부적인 로직을 살펴보지 못해 아쉽다.
    • 하지만 성취감에 만족한다.
  • 라우팅 처리에 대한 부분도 살펴보고 싶다. 어느 프로젝트나 비슷하겠지만, 궁금하다. 어떻게 처리 하는지.


'삽질의 현장 > - Opensource' 카테고리의 다른 글

SonarQube는 어떻게 Routing을 할까?  (0) 2019.02.23