sonarQube-iOS-Objective-C客戶端搭建手冊(cè)

sonarQube分為服務(wù)端和客戶端抒和。服務(wù)端相當(dāng)于一個(gè)webService茵肃,將客戶端代碼掃描結(jié)果通過(guò)web方式進(jìn)行展示,可以安裝在代碼本地虎囚,也可以安裝在另外一臺(tái)機(jī)器淮椰♂牵客戶端也叫sonar-scanner,用于收集掃描代碼結(jié)果并上傳到服務(wù)端实苞,需要安裝在代碼端。
sonarQube針對(duì)OC有一個(gè)官方的插件烈疚,但是收費(fèi)的黔牵,為了省錢(qián),下面介紹開(kāi)源的插件爷肝。

插件地址:https://github.com/Backelite/sonar-swift

服務(wù)端插件安裝

插件是運(yùn)行在sonarQube服務(wù)端的猾浦,下載https://github.com/Backelite/sonar-swift/releases最新版本(目前為0.4.5),插件文件是個(gè)jar包灯抛,需要拷貝到sonarQube服務(wù)端插件安裝目錄(/sonarqube/extensions/plugins)金赦,然后重啟sonarQube服務(wù)端,重啟后登陸对嚼,進(jìn)入sonarQube->Administration->Marketplace夹抗,查看插件列表中是否有Swift (Backelite),如果有纵竖,則表示安裝成功

代碼端本地配置

本地配置工作比較多漠烧,且遇到的坑也特別多,下面一一介紹

該插件雖然支持對(duì)Objective-CSwift的統(tǒng)計(jì)靡砌,由于我的項(xiàng)目中沒(méi)有Swift代碼已脓,所以涉及Swift的地方基本一筆帶過(guò)了

sonar-scanner

sonar-scannersonarQube的客戶端,用于將各個(gè)命令執(zhí)行生成的結(jié)果上傳到服務(wù)端

官方參考:https://docs.sonarQube.org/latest/analysis/scan/sonarscanner/

安裝完后通殃,要把bin目錄加到環(huán)境變量中

run-sonar-swift

run-sonar-swift.sh是替代sonar-scanner命令的一個(gè)shell腳本度液,在執(zhí)行sonar-scanner的時(shí)候,使用run-sonar-swift.sh即可

腳本基本原理:

1. 讀取命令參數(shù)和`sonar-project.properties`的設(shè)置
2. 生成compile_commands.json
3. 生成coverage的xml報(bào)告
4. 執(zhí)行SwiftLint和Tailor檢查Swift画舌,我這里沒(méi)有Swift堕担,直接沒(méi)裝這兩個(gè)工具
5. 執(zhí)行oclint
6. 執(zhí)行Lizard
7. 執(zhí)行sonar-scanner

安裝方式:

clone代碼到本地https://github.com/Backelite/sonar-swift

然后拷貝sonar-swift/sonar-swift-plugin/src/main/shell/run-sonar-swift.shsonar-scanner/.../bin/目錄下,

xcpretty

對(duì)xcodebuild的輸出進(jìn)行格式化的工具骗炉,生成報(bào)告照宝,增加可讀性

直接安裝最新版本,Backelite中說(shuō)的問(wèn)題已在最新版中改正
官方參考:https://github.com/xcpretty/xcpretty

SwiftLint

Swift語(yǔ)言的靜態(tài)檢測(cè)工具,目前iOS全部為Objective-C語(yǔ)言句葵,沒(méi)有swift厕鹃,所以暫時(shí)不用安裝

Tailor

Swift語(yǔ)言的靜態(tài)檢測(cè)工具兢仰,目前iOS全部為Objective-C語(yǔ)言,沒(méi)有swift剂碴,所以暫時(shí)不用安裝

slather

語(yǔ)言:ruby

用于將xcode生成的coverage報(bào)告轉(zhuǎn)換成xml格式把将,核心命令是使用llvm-cov命令

安裝方式:gem install slather

官網(wǎng):https://github.com/SlatherOrg/slather

此處有坑:

坑一:static library在使用llvm-cov時(shí)會(huì)出錯(cuò),錯(cuò)誤信息如下

Failed to load coverage: Malformed coverage data
error: Could not load coverage information
Traceback (most recent call last):
    16: from /usr/local/Cellar/ruby/2.6.1/bin/slather:23:in `<main>'
    15: from /usr/local/Cellar/ruby/2.6.1/bin/slather:23:in `load'
    14: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/slather-2.4.7/bin/slather:17:in `<top (required)>'
    13: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/clamp-1.3.1/lib/clamp/command.rb:140:in `run'
    12: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/clamp-1.3.1/lib/clamp/command.rb:66:in `run'
    11: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/clamp-1.3.1/lib/clamp/subcommand/execution.rb:18:in `execute'
    10: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/clamp-1.3.1/lib/clamp/command.rb:66:in `run'
     9: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/slather-2.4.7/lib/slather/command/coverage_command.rb:59:in `execute'
     8: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/slather-2.4.7/lib/slather/command/coverage_command.rb:97:in `post'
     7: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/slather-2.4.7/lib/slather/coverage_service/cobertura_xml_output.rb:18:in `post'
     6: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/slather-2.4.7/lib/slather/project.rb:98:in `coverage_files'
     5: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/slather-2.4.7/lib/slather/project.rb:123:in `profdata_coverage_files'
     4: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/slather-2.4.7/lib/slather/project.rb:123:in `each'
     3: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/slather-2.4.7/lib/slather/project.rb:124:in `block in profdata_coverage_files'
     2: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/slather-2.4.7/lib/slather/project.rb:135:in `pathnames_per_binary'
     1: from /usr/local/Cellar/ruby/2.6.1/lib/ruby/2.6.0/json/common.rb:156:in `parse'
/usr/local/Cellar/ruby/2.6.1/lib/ruby/2.6.0/json/common.rb:156:in `parse': 767: unexpected token at '' (JSON::ParserError)

原因:
slather使用llvm-cov命令將coverage轉(zhuǎn)換為xml忆矛,但llvm-cov貌似對(duì)靜態(tài)庫(kù)不支持察蹲,命令執(zhí)行會(huì)出錯(cuò)

解決方案:

  • 修改run-sonar-swift.sh腳本,添加MACH_O_TYPE=mh_dylibxcodebuild test命令中催训,生成結(jié)果為動(dòng)態(tài)庫(kù)形式洽议,這樣就可以正常生成xml
buildCmd+=( -scheme "$appScheme" -configuration "$appConfiguration" -enableCodeCoverage YES MACH_O_TYPE=mh_dylib)

坑二:slather生成的coverage-swift.xml中如果存在line number="0"的情況,在調(diào)用sonar-scanner上傳的時(shí)候會(huì)報(bào)錯(cuò)漫拭,如下

//coverage-swift.xml中存在number="0"的情況

<class name="uSDKConst.h" filename="Modules/uSDKCommon/uSDKCommon/uSDKConst.h" line-rate="0.7586206896551724" branch-rate="1.0000000000000000" complexity="0.0">
          <methods/>
          <lines>
            <line number="0" branch="false" hits="1910"/>
            <line number="0" branch="false" hits="1500"/>

//在sonar-scanner上傳時(shí)會(huì)出錯(cuò)
ERROR: Error during SonarQube Scanner execution
java.lang.IllegalStateException: Line number must be strictly positive: 0
    at org.sonar.api.internal.google.common.base.Preconditions.checkState(Preconditions.java:197)
    at org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage.validateLine(DefaultCoverage.java:94)
    at org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage.lineHits(DefaultCoverage.java:81)
    at com.backelite.sonarqube.swift.coverage.CoberturaReportParser.collectFileData(CoberturaReportParser.java:109)
    at com.backelite.sonarqube.swift.coverage.CoberturaReportParser.collectClassMeasures(CoberturaReportParser.java:91)
    at com.backelite.sonarqube.swift.coverage.CoberturaReportParser.collectPackageMeasures(CoberturaReportParser.java:79)
    at com.backelite.sonarqube.swift.coverage.CoberturaReportParser.parseReport(CoberturaReportParser.java:61)
    at com.backelite.sonarqube.swift.coverage.CoberturaSensor.execute(CoberturaSensor.java:69)
    at org.sonar.scanner.sensor.AbstractSensorWrapper.analyse(AbstractSensorWrapper.java:48)
    at org.sonar.scanner.sensor.ModuleSensorsExecutor.execute(ModuleSensorsExecutor.java:85)
    at org.sonar.scanner.sensor.ModuleSensorsExecutor.lambda$execute$1(ModuleSensorsExecutor.java:59)
    at org.sonar.scanner.sensor.ModuleSensorsExecutor.withModuleStrategy(ModuleSensorsExecutor.java:77)
    at org.sonar.scanner.sensor.ModuleSensorsExecutor.execute(ModuleSensorsExecutor.java:59)
    at org.sonar.scanner.scan.ModuleScanContainer.doAfterStart(ModuleScanContainer.java:82)
    at org.sonar.core.platform.ComponentContainer.startComponents(ComponentContainer.java:136)
    at org.sonar.core.platform.ComponentContainer.execute(ComponentContainer.java:122)
    at org.sonar.scanner.scan.ProjectScanContainer.scan(ProjectScanContainer.java:400)
    at org.sonar.scanner.scan.ProjectScanContainer.scanRecursively(ProjectScanContainer.java:395)
    at org.sonar.scanner.scan.ProjectScanContainer.doAfterStart(ProjectScanContainer.java:358)
    at org.sonar.core.platform.ComponentContainer.startComponents(ComponentContainer.java:136)
    at org.sonar.core.platform.ComponentContainer.execute(ComponentContainer.java:122)
    at org.sonar.scanner.bootstrap.GlobalContainer.doAfterStart(GlobalContainer.java:141)
    at org.sonar.core.platform.ComponentContainer.startComponents(ComponentContainer.java:136)
    at org.sonar.core.platform.ComponentContainer.execute(ComponentContainer.java:122)
    at org.sonar.batch.bootstrapper.Batch.doExecute(Batch.java:73)
    at org.sonar.batch.bootstrapper.Batch.execute(Batch.java:67)
    at org.sonarsource.scanner.api.internal.batch.BatchIsolatedLauncher.execute(BatchIsolatedLauncher.java:46)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at org.sonarsource.scanner.api.internal.IsolatedLauncherProxy.invoke(IsolatedLauncherProxy.java:60)
    at com.sun.proxy.$Proxy0.execute(Unknown Source)
    at org.sonarsource.scanner.api.EmbeddedScanner.doExecute(EmbeddedScanner.java:189)
    at org.sonarsource.scanner.api.EmbeddedScanner.execute(EmbeddedScanner.java:138)
    at org.sonarsource.scanner.cli.Main.execute(Main.java:112)
    at org.sonarsource.scanner.cli.Main.execute(Main.java:75)
    at org.sonarsource.scanner.cli.Main.main(Main.java:61)

原因:
coverage-swift.xml中存在number="0"的情況

解決方案:

  • 在工程根目錄下新增.slather.yml文件亚兄,采用配置文件方式執(zhí)行slather命令,注意yml文件的格式
  • .slather.yml中配置ignore采驻,將coverage-swift.xmlline number="0"的文件添加到ignore中审胚,對(duì)ruby實(shí)在不懂,目前沒(méi)找到直接忽略目錄的方式礼旅,所以采用單個(gè)文件添加
# .slather.yml
coverage_service: cobertura_xml
xcodeproj: project_path.xcodeproj
scheme: YourXcodeSchemeName
source_directory: source_dir
output_directory: sonar-reports
input-format: profdata
ignore:
    - number_0_file_1.h
    - number_0_file_2.h
    - number_0_file_3.h
  • run-sonar-swift.sh中的slather命令可改可不改膳叨,我是沒(méi)有改,因?yàn)橹挥?code>.slather.yml中的ignore字段痘系,跟原命令不沖突

待解決 .slather.yml如何精準(zhǔn)配置source和ignore菲嘴,目前不能配置ignore的目錄方式,且生成的xml中依然有tests.m文件

lizard

語(yǔ)言:python

復(fù)雜度分析工具碎浇,安裝方式:sudo pip install lizard

官網(wǎng):https://github.com/terryyin/lizard

此處有坑:

坑一:腳本中的lizard命令不支持sonar.sources為多路徑的情況

解決方案:

//修改腳本中l(wèi)izard命令
paths=`tr ',' ' ' <<< "${srcDirs}"`
$LIZARD_CMD --xml -l objectivec $paths > sonar-reports/lizard-report.xml

OCLint

OCLintObjective-C語(yǔ)言的靜態(tài)檢測(cè)工具, 目前使用homebrew所能安裝的最新版本是0.13临谱,但不適用于最新的xcode11(可能xcode10都不支持,各種報(bào)錯(cuò)), 需要自己源碼編譯安裝最新的0.15版本奴璃,下面主要介紹源碼編譯的方法悉默,編譯過(guò)程可以在自己本機(jī)即可。

編譯&安裝方法:

  1. https://github.com/oclint/oclint/releases下載0.15版本的源碼

  2. http://releases.llvm.org/download.html#9.0.0下載已編譯的llvm9.0

注意:

  • 這里安裝llvm9.0是因?yàn)?code>oclint0.15 release中說(shuō)明了對(duì)llvm版本的要求
  • 下載直接編譯好的llvm是因?yàn)槿绻捎?code>oclint官網(wǎng)推薦的./make安裝方式苟穆,會(huì)下載llvm源碼并編譯抄课,其過(guò)程非常非常漫長(zhǎng)也容易失敗,不可取雳旅。跟磨。。
  1. 編譯
1. cd到oclint-scripts目錄下
cd oclint-0.15/oclint-scripts

2. 編譯攒盈,注意參數(shù)為llvm的絕對(duì)路徑
./makeWithSystemLLVM /absolute/llvm/path/clang+llvm-9.0.0-x86_64-darwin-apple/

3. 編譯完成后會(huì)在oclint-0.15目錄下生成一個(gè)build/oclint-release目錄抵拘,即為編譯完成的oclint
  1. 安裝
1. cd 到oclint-release目錄下
cd oclint-0.15/build/oclint-release

2. 拷貝oclint到代碼端的系統(tǒng)路徑
cp bin/oclint* /usr/local/bin/
cp -rp lib/* /usr/local/lib/
cp -rp include/* /usr/local/include/

sonar-project.properties

sonar-project.propertiessonarQube在客戶端的配置文件,一般放置于工程根目錄

sonar-project.properties在工程的根目錄型豁,sonar-scanner的安裝目錄和服務(wù)器上都可以配置僵蛛,優(yōu)先級(jí)是根目錄 > sonaer-scanner安裝目錄 > 服務(wù)器尚蝌,即在自己的工程根目錄的配置就會(huì)覆蓋其他地方的配置

#scm的問(wèn)題見(jiàn)文章最下方
sonar.scm.disabled=true
sonar.host.url=http://xxx:9000

sonar.projectKey=iOS
sonar.projectName=iOSProjectName
sonar.projectVersion=1.0
sonar.language=objc
sonar.projectDescription=projectDescription
sonar.sourceEncoding=UTF-8

#源文件目錄飘言,這里采用精確到具體目錄的方式驼侠,為了不配置inclusions和exclusions倒源,因?yàn)樵谑褂玫倪^(guò)程發(fā)現(xiàn)笋熬,如果oclint或lizard產(chǎn)生的結(jié)果集比sonar.sources配置的大,上傳時(shí)容易出錯(cuò),所以最好精確的指定sonar.sources
sonar.sources=relative/dir1,relative/dir2,relative/dir3

#要包含的文件旺隙,注意不是目錄
sonar.inclusions=./**/*.h,./**/*.m
sonar.exclusions=./exclude/**/*,./**/*Tests*

#下面這些配置是run-sonar-swift.sh中定義的
sonar.swift.simulator=platform=iOS Simulator,name=iPhone 11
sonar.swift.appScheme=appScheme
sonar.swift.project=your_project.xcodeproj

運(yùn)行

cd到工程根目錄蔬捷,即sonar-project.properties所在位置周拐,執(zhí)行run-sonar-swift就可以了

SCM導(dǎo)致的問(wèn)題通常表現(xiàn)如下:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末妥粟,一起剝皮案震驚了整個(gè)濱河市勾给,隨后出現(xiàn)的幾起案子锅知,更是在濱河造成了極大的恐慌,老刑警劉巖桩警,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捶枢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡川蒙,警方通過(guò)查閱死者的電腦和手機(jī)畜眨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)康聂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)恬汁,“玉大人氓侧,你說(shuō)我怎么就攤上這事约巷『蹬酰” “怎么了枚赡?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵氓癌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我贫橙,道長(zhǎng),這世上最難降的妖魔是什么卢肃? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮践剂,結(jié)果婚禮上鬼譬,老公的妹妹穿的比我還像新娘。我一直安慰自己逊脯,他們只是感情好优质,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般演怎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上爷耀,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼爹橱。 笑死萨螺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的组砚。 我是一名探鬼主播惹盼,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼惫确!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蚯舱,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤改化,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后枉昏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體陈肛,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年兄裂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了句旱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晰奖,死狀恐怖谈撒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情匾南,我是刑警寧澤啃匿,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響溯乒,放射性物質(zhì)發(fā)生泄漏夹厌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一裆悄、第九天 我趴在偏房一處隱蔽的房頂上張望矛纹。 院中可真熱鬧,春花似錦光稼、人聲如沸或南。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)迎献。三九已至,卻和暖如春腻贰,著一層夾襖步出監(jiān)牢的瞬間吁恍,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工播演, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冀瓦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓写烤,卻偏偏與公主長(zhǎng)得像翼闽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子洲炊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 前言 團(tuán)隊(duì)開(kāi)發(fā)中感局,代碼質(zhì)量的把關(guān),往往決定了一個(gè)團(tuán)隊(duì)的開(kāi)發(fā)維護(hù)效率暂衡。成員的增長(zhǎng)询微,業(yè)務(wù)的擴(kuò)大,不同風(fēng)格狂巢、不嚴(yán)謹(jǐn)?shù)拇a...
    生光閱讀 5,779評(píng)論 1 3
  • 本文重點(diǎn)說(shuō)明sonarqube的使用和搭建,以及集成到Jenkins撑毛,從因到果進(jìn)行詳細(xì)的說(shuō)明。gitLab+Jen...
    GeekSpring閱讀 25,546評(píng)論 1 7
  • 前序步驟:一唧领、《MAC Jenkins安裝》二藻雌、《iOS持續(xù)構(gòu)建-編譯打包上傳》三、《iOS+Jenkins持續(xù)構(gòu)...
    圣艾修閱讀 6,081評(píng)論 4 6
  • 一斩个、前言 年初的時(shí)候部門(mén)各組都給出了自己的規(guī)范文檔胯杭,包括部門(mén)工作規(guī)范、各語(yǔ)言開(kāi)發(fā)規(guī)范受啥、測(cè)試規(guī)范歉摧、數(shù)據(jù)庫(kù)規(guī)范、安全規(guī)...
    dancingking閱讀 15,136評(píng)論 8 20
  • 我從不相信這個(gè)世界是美好的,但我依然會(huì)用善心觀看這個(gè)世界叁温;因?yàn)槲蚁嘈旁俚浚谶@個(gè)并不美好的世界上,如果再缺少了善心的關(guān)...
    楊嘉利閱讀 183評(píng)論 0 0