Share
Embed
痒いところに手を届かせるArchUnitの利用法 ~アーキテクチャーテストからアプリケーション解析まで~
2020/11/07_日本Javaユーザグループ主催「JJUG CCC 2020 Fall」での、小谷野の講演資料になります
https://ccc2020fall.java-users.jp/
【動画】https://www.youtube.com/watch?v=tigesXMmTpc&list=PLy44EKO1L0eIxmapLWe6_60nJwt_EAsjU&index=2
#conference #engineering
Transcript
103 slides
-
͜ͷηογϣϯͷత w ϗοτϖούʔϏϡʔςΟʔͷϞόΠϧΞϓϦ͚"1*ͱ "OESPJEΞϓϦʹ͓͚Δ"SDI6OJUͷར༻ࣄྫΛհ ࢼߦࡨޡதͷͷؚΈ·͢ w "SDI6OJUΛར༻ͨ͠ΞʔΩςΫνϟͷ୯ମςετ͔Β ੩తղੳΛར༻ͨ͠ΞϓϦέʔγϣϯղੳ·Ͱ ༷ʑͳ"SDI6OJUͷ͍ํΛͬͯΒ͏
w ͔Β࣮͠Α͏ͱ͢Δͱେม͚ͩͲɺ͠Ͱ͖ͨΒخ͍ࣗ͠ಈԽΛ "SDI6OJUΛ࣮ͬͯફ͍ͨ͠ͱࢥͬͯΒ͏ -
8 IUUQTXXXBSDIVOJUPSHVTFSHVJEFIUNM@*OEFYIUNM JUnitͱͷ࿈ܞ @RunWith(ArchUnitRunner.class) // JUnit5Ͱෆཁ @AnalyzeClasses(packages = "com.myapp") public
class ArchitectureTest { @ArchTest public static final ArchRule rule1 = classes() .should()... @ArchTest public static final ArchRule rule2 = classes() .should()... @ArchTest public static void rule3(JavaClasses classes) { // } } w +6OJUͱ࿈ܞͤ͞Δ͜ͱͰ "SVD6OJUͰ੍࣮ͨ͠Λ ͦͷ··ςετϝιου ͱͯ͠ఆٛͰ͖Δ w ଞͷςετϑϨʔϜϫʔΫʹ ΈࠐΉ͜ͱՄೳ -
9 classes() .that() .resideInAPackage("..foo..") .should() .onlyHaveDependentClassesThat() .resideInAnyPackage( "..source.one..", "..foo.."
); ੍ΛJavaίʔυͷUTͰ࣮Ͱ͖Δ IUUQTXXXBSDIVOJUPSHVTFSHVJEFIUNM@*OEFYIUNM -
10 classes() .that() .haveNameMatching(".*Bar") .should() .onlyBeAccessed() .byClassesThat() .haveSimpleName("Bar"); Ϋϥεͷґଘؔͷ੍ςετ
IUUQTXXXBSDIVOJUPSHVTFSHVJEFIUNM@*OEFYIUNM -
11 classes() .that() .implement(Connection.class) .should() .haveSimpleNameEndingWith( "Connection" ); ໋໊نͷ੍ςετ
IUUQTXXXBSDIVOJUPSHVTFSHVJEFIUNM@*OEFYIUNM -
12 IUUQTXXXBSDIVOJUPSHVTFSHVJEFIUNM@*OEFYIUNM ஈ֊తͳςετͷಋೖ w ͋Δ࣌ͷΞʔΩςΫνϟʔςετͷ ࣮ߦ݁ՌͷεφοϓγϣοτΛར༻ͯ͠ ஈ֊తͳςετͷಋೖ͕Մೳ w Ұ෦ͷίʔυͷ੍ҧΛແࢹͯ͠ ৽͕ࠩ͋ͬͨ͘͠ίʔυʹର͚ͯͩ͠
ΞʔΩςΫνϟʔͷ੍Λڧ੍Ͱ͖Δ ArchRule rule = FreezingArchRule .freeze( classes().should()... ); -
ಋೖϢʔεέʔεྫ w ґଘ੍ؔɾ໋໊نଇͳͲΛςετͱ࣮ͯͯ͠͠ڧ੍͢Δ ΞʔΩςΫνϟʔϨϏϡʔͷࣗಈԽ ಛఆͷ࣮ϧʔϧͷ࿙ΕΛ͙ ςετͱͯ͠ͷυΩϡϝϯςʔγϣϯʹΑΔΞʔΩςΫνϟʔͷ໌จԽ w ஈ֊తͳΞʔΩςΫνϟʔҠߦɾඋͷαϙʔτ ϦϑΝΫλϦϯάɾϦΞʔΩςΟϯάରͷίʔυʹ෦తʹ65Λద༻ w
ΞϓϦέʔγϣϯղੳͷϑϨʔϜϫʔΫͱͯ͠ར༻ ґଘؔʹؔ͢ΔใͷυΩϡϝϯτͷࣗಈੜ 13 -
16 // ΫϥεϑΝΠϧ͔ΒJavaClassesΠϯελϯεΛऔಘ JavaClasses importedClasses = new ClassFileImporter() //
ςετίʔυͷbytecodeআ֎͢Δ .withImportOption( ImportOption.d.DO_NOT_INCLUDE_TESTS ) // Ϋϥεύε্ͷbytecodeΛಡΈࠐΉ .importClasspath(); Core APIͱbytecode w $MBTT'JMF*NQPSUFSʹΑͬͯ ࢦఆͨ͠ΫϥεϑΝΠϧͷ CZUFDPEFΛಡΈࠐΉ w "4.ϥΠϒϥϦΛར༻ͯ͠ CZUFDPEFΛղੳͯ͠ "SDI6OJUݻ༗ͷදݱʹม͠ $PSF"1*Λհͯ͠։ൃऀʹ ใΛఏڙ -
19 w Πϯϙʔτͨ͠CZUFDPEF্ͷ ΫϥεɾϝιουɾϑΟʔϧυͳͲͷཁૉؒͷ ΞΫηεΛશͯه͠ґଘؔάϥϑΛߏங w ґଘཁૉʹΞΫηε͢ΔͨΊͷ"1*Λఏڙ FYHFU'JFME"DDFTTFT'SPN4FMG ϑΟʔϧυʹΞΫηε͍ͯ͠ΔཁૉΛऔಘ
HFU.FUIPE$BMMT5P4FMG ϝιου͕෦Ͱݺͼग़͍ͯ͠ΔϝιουΛऔಘ IUUQTXXXBSDIVOJUPSHVTFSHVJEFIUNM@*OEFYIUNM Core APIͱґଘؔ -
20 // serviceͱ͍͏จࣈΛؚΉpackageԼͷΫϥε // controllerͱ͍͏จࣈΛؚΉpackageԼͷΫϥεʹ // ΞΫηε͍͚ͯ͠ͳ͍ ArchRule rule
= ArchRuleDefinition .noClasses() .that() .resideInAPackage("..service..") .should() .accessClassesThat() .resideInAPackage("..controller.."); rule.check(importedClasses); Lang API w $PSF"1*͔ΒऔಘͰ͖ΔใΛ ݩʹґଘؔͷ੍Λॊೈ͔ͭ ؆ܿʹ࣮͢ΔͨΊͷ"1* w ੍ʹҧ͢ΔΫϥεͳͲ͕ ଘࡏͨ͠߹ʹςετΛ ࣦഊͤ͞Δ͜ͱ͕Ͱ͖Δ w جຊతʹ͜ͷ"1*Λͬͯ ςετΛ࣮͢Δ͜ͱ͕ଟ͍ -
21 // ݕࠪରͷΫϥε͕@PayloadΞϊςʔγϣϯ͕༩͞Εͨ // ϑΟʔϧυΛ͍࣋ͬͯΔ͔ΛνΣοΫ͢ΔΧελϜϧʔϧ DescribedPredicate<JavaClass> haveAFieldAnnotatedWithPayload = new
DescribedPredicate<>( "have a field annotated with @Payload” ) { @Override public boolean apply(JavaClass input) { return checkCustomRule(input); } }; Lang APIͱΧελϜϧʔϧ w %FTDSJCFE1SFEJDBUFͳͲͷ ΠϯλʔϑΣʔεΛ࣮ͯ͠ ಠࣗͷΧελϜϧʔϧΛ ࡞͢Δ͜ͱ͕Մೳ w ࣮ݱ͍ͨ͠ϧʔϧʹରԠ͢Δ "1*͕ଘࡏ͠ͳ͍߹ -BOH"1*ͱ$PSF"1*Λ Έ߹Θ੍ͤͯΛ࣮͢Δ -
22 Library API w ࢦఆͨ͠ύοέʔδΛ άϧʔϐϯάͯ͠యܕతͳ ΞʔΩςΫνϟʔͷ ϨΠϠʔͱඥ͚Δ "1*Λఏڙ
w ϨΠϠʔͷґଘ੍ؔΛ ͦͷ··ΞϓϦέʔγϣϯʹ ద༻͢Δ͜ͱ͕Ͱ͖Δ layeredArchitecture() .layer("Controller").definedBy("..controller..") .layer("Service").definedBy("..service..") .layer("Persistence").definedBy("..persistence..") .whereLayer("Controller").mayNotBeAccessedByAnyLayer() .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller") .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service"); -
23 Library API layeredArchitecture() .layer("Controller").definedBy("..controller..") .layer("Service").definedBy("..service..") .layer("Persistence").definedBy("..persistence..") .whereLayer("Controller").mayNotBeAccessedByAnyLayer() .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
.whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service"); -
w "SDI6OJUͷಛ ઃఆϑΝΠϧ%4-Ͱͳ͘ϥΠϒϥϦ͕ఏڙ͢Δ"1*Λ༻͍ͨ +BWBίʔυͰ+6OJUͳͲͷ୯ମςετͱͯ͠هड़Ͱ͖Δ w ྨࣅͷ͜ͱ͕Ұ෦࣮ݱͰ͖Δπʔϧ ੍ݕࠪɿ"TQFDU+ $IFDLTUZMF 'JOE#VHT ґଘؔɿK2"TTJTUBOU
%FQFOE %FHSBQI %FQUFDUJWF ArchUnitͷಛͱྨࣅπʔϧͱͷൺֱ 24 -
w "SDI6OJUͷಛ ઃఆϑΝΠϧ%4-Ͱͳ͘ϥΠϒϥϦ͕ఏڙ͢Δ"1*Λ༻͍ͨ +BWBίʔυͰ+6OJUͳͲͷ୯ମςετͱͯ͠هड़Ͱ͖Δ w ྨࣅͷ͜ͱ͕Ұ෦࣮ݱͰ͖Δπʔϧ ੍ݕࠪɿ"TQFDU+ $IFDLTUZMF 'JOE#VHT ґଘؔɿK2"TTJTUBOU
%FQFOE %FHSBQI %FQUFDUJWF ArchUnitͷಛͱྨࣅπʔϧͱͷൺֱ 25 "SDI6OJUͷศརͦ͏ͳ͜ͱΘ͔Δɻ ͚ͩͲɺ࣮ࡍʹͲ͏͍͏͍ํΛͯ͠ ΞϓϦέʔγϣϯʹಋೖ͍ͯ͠Δͷ͔ʁ Α͋͘Δٙ -
w ϔΞαϩϯɾϦϥΫϏϡʔςΟαϩϯͷ ݕࡧɾ༧αʔϏε w ϞόΠϧΞϓϦ J04"OESPJE 8FC 1$ɾεϚʔτϑΥϯ
ʹͯల։ w ϞόΠϧΞϓϦ͚"1*ͱ "OESPJEΞϓϦʹ"SDI6OJUͷಋೖ ϗοτϖούʔϏϡʔςΟʔͱ 27 IUUQTCFBVUZIPUQFQQFSKQEPDHVJEFTBJTIJOEBUBIUNM -
ϞόΠϧΞϓϦ͚APIͷΞʔΩςΫνϟʔ #BDLFOE "1* GPS403 #''"1* GPS40& J04"OESPJE ΞϓϦ %#ݕࡧΤϯδϯ 28
w #'' #BDLFOET'PS'SPOUFOET Λར༻ͨ͠ ϚΠΫϩαʔϏεΞʔΩςΫνϟʔΛಋೖ w ΞϓϦέʔγϣϯنνʔϜߏΛߟྀͨ͠40&ɾ403ͷʹΑͬͯ "1*։ൃεϐʔυͷߴԽΛࢦ͢ -
#BDLFOE"1* #''"1* දࣔཁ݅ʹؔΘΔॲཧ 40& ΞϓϦಛ༗ͷॲཧ ΞϓϦͷϢʔεέʔεʹ߹Θͤͨ"1* 4QSJOH8FC'MVY,PUMJO 29 ٕज़ελοΫͷҧ͍
4QSJOH.7$+BWB σʔλͷӬଓԽʹؔΘΔॲཧ 403 ۀϩδοΫ Ϧιʔεࢦͷ"1* -
ϞόΠϧΞϓϦ͚APIͷΞʔΩςΫνϟʔ #BDLFOE "1* GPS403 #''"1* GPS40& J04"OESPJE ΞϓϦ %#ݕࡧΤϯδϯ 30
w ΞϓϦͷϢʔεέʔε୯ҐͰ࡞ͨ͠#''ͷ"1* σʔλͷϦιʔε୯ҐͰ࡞ͨ͠#BDLFOEͷ"1*͔Β ඞཁͳใΛूͯ͠ΞϓϦʹఏڙ A C A B C B -
#BDLFOE"1* #''"1* 31 ιϑτΣΞΞʔΩςΫνϟʔ $POUSPMMFSΫϥε 3FQPTJUPSZΫϥε 6TF$BTFΫϥε υϝΠϯϞσϧ $POUSPMMFSΫϥε 3FQPTJUPSZΫϥε
6TF$BTFΫϥε 4FSWJDFΫϥε ҹґଘؔͷ͖ -
33 (༨ஊ) αʔϏεΛ͞Βʹׂ͠ͳ͍ͷ͔ʁ w νʔϜߏٕज़ཁҼ͔Β8FCΞϓϦ͚ͷ#'' #BDLFOE"1*ͷυϝΠϯׂ࣮ࢪ͍ͯ͠ͳ͍ঢ়ଶ w ඞཁੑ͕͋Εࠓޙ࣮ࢪ͢ΔՄೳੑ͋Δ #BDLFOE"1* #''GPS8FC
33 #BDLFOE"1* #BDLFOE"1* #''GPS"QQ -
w جຊతͳΞʔΩςΫνϟʔཧղ ઃܭυΩϡϝϯτΛ௨ͯ͡ νʔϜϝϯόʔʹਁಁ͍ͯ͠Δঢ়ଶ w 13্ͰϨΠϠʔؒͷґଘؔʹ ؔ͢Δࢦఠ͕ൃੜ͢Δ͜ͱͳ͘ ϨΠϠʔʹؔ͢ΔΞʔΩςΫνϟʔ ςετಛʹඞཁͱ͍ͯ͠ͳ͍ঢ়ଶ എܠɿϨΠϠʔؒͷґଘؔ
38 $POUSPMMFSΫϥε 3FQPTJUPSZΫϥε 6TF$BTFΫϥε υϝΠϯϞσϧ -
TRYɿಛఆ݅ԼͰͷΠϯϙʔτΛࢭ 41 @RunWith(ArchUnitRunner.class) @AnalyzeClasses(packages = BASE_PACKAGE) public class HairKireiDependencyTest
{ @ArchTest static final ArchRule hairύοέʔδͷΫϥε͕kireiύοέʔδͷΫϥεʹґଘͯ͠ͳ͍͜ͱΛ֬ೝ = classes() .that() // ύοέʔδ໊ͷύεʹhairΛؚΉΫϥεΛநग़ .resideInAPackage("..hair..") // Ұ෦ͷྫ֎Λআ͍ͯɺύοέʔδ໊ͷύεʹkireiΛؚΉΫϥεͷґଘΛېࢭ .should(ArchConditions.onlyDependOnClassesThat(NOT_DEPEND_KIREI_CLASS)); } -
TRYɿಛఆ݅ԼͰͷΠϯϙʔτΛࢭ 42 private static final DescribedPredicate<JavaClass> NOT_DEPEND_KIREI_CLASS = new
DescribedPredicate<>("must not depend on classes in kirei package") { @Override public boolean apply(JavaClass input) { if (!input.getPackageName().contains("kirei")) { return true; } // @KireiResourceUsedByHairΞϊςʔγϣϯ͕༩͞Ε͍ͯΔΫϥεྫ֎తʹґଘΛڐՄ͢Δ if (input.isAnnotatedWith(KireiResourceUsedByHair.class)) { return true; } } }; -
ิɿϞδϡʔϧׂͱͷൺֱ 44 ϨΠϠʔͷґଘؔΛ੍ޚ͢Δ IUUQTXXXPSFJMMZDPNMJCSBSZWJFX TPGUXBSFBSDIJUFDUVSFQBUUFSOT w ϞδϡʔϧׂͰґଘؔΛ ੍ޚͰ͖͍ͯΔͳΒͦΕͰे w ྫ֎έʔεׂ͕͋ͬͯͮ͠Β͍
·ׂͩ͢Δ΄ͲͷنͰͳ͍ কདྷͷׂʹઌཱͬͨୈҰา ͱ͍ͬͨ߹ʹ༗༻ -
IUUQTUXJUUFSDPNKBIOOJFTUBUVT 46 w ༏ઌ֎෦ڥͳͲͷཁҼͰ ࣮͕Ұ෦ྃͭͭ͠ ͳ͔ͳ͔ϦϦʔεͰ͖ͳ͍ϒϥϯν͕ଘࡏ w ϒϥϯνͷϥΠϑλΠϜ͕ෆ༻ҙʹ͘ ͳͬͯ͠·͓ͬͯΓίϯϑϦΫτ͕
ൃੜ͘͢͠ͳ͍ͬͯͨ എܠɿฏߦ͢Δϒϥϯνͱίʔυͷෳࡶ͞ -
എܠɿfeatureτάϧʹΑΔϒϥϯνͷݮ 47 IUUQTXXXNBSUJOGPXMFSDPNBSUJDMFTGFBUVSFUPHHMFTIUNM w ฏߦ͢ΔϒϥϯνͷΛݮΒͨ͢Ίʹ ੩తͳϦϦʔετάϧΛಋೖͯ͠ ϝΠϯϒϥϯνʹϚʔδ w ࣮ͨͩݧతʹϦϦʔε͢Δ༰Ͱ͋Γ
༷͕ॾࣄͰෆ҆ఆͳঢ়ଶͩͬͨ w ͕ͨͬͯ͠ଞͷ҆ఆͨ͠ίʔυ͔Β ෆ҆ఆͳίʔυʹґଘͤͨ͘͞ͳ͍ -
՝ɿෆ҆ఆͳίʔυ͕ϝΠϯϥΠϯʹ߹ྲྀ 48 #PPLNBSL6TFDBTF #PPLNBSL3FQPTJUPSZ υϝΠϯϞσϧ $PVQPO6TFDBTF #PPLNBSL4FSWJDF $PVQPO4FSWJDF
$PVQPO3FQPTJUPSZ υϝΠϯϞσϧ ҹґଘؔͷ͖ &YQFSJNFOUBM6TFDBTF &YQFSJNFOUBM4FSWJDF &YQFSJNFOUBM3FQPTJUPSZ υϝΠϯϞσϧ -
՝ɿෆ҆ఆͳίʔυ͕ϝΠϯϥΠϯʹ߹ྲྀ 49 #PPLNBSL6TFDBTF #PPLNBSL3FQPTJUPSZ υϝΠϯϞσϧ $PVQPO6TFDBTF &YQFSJNFOUBM6TFDBTF #PPLNBSL4FSWJDF
$PVQPO4FSWJDF &YQFSJNFOUBM4FSWJDF $PVQPO3FQPTJUPSZ &YQFSJNFOUBM3FQPTJUPSZ υϝΠϯϞσϧ ҹґଘؔͷ͖ υϝΠϯϞσϧ -
՝ɿෆ҆ఆͳίʔυ͕ϝΠϯϥΠϯʹ߹ྲྀ 50 #PPLNBSL6TFDBTF #PPLNBSL3FQPTJUPSZ υϝΠϯϞσϧ $PVQPO6TFDBTF &YQFSJNFOUBM6TFDBTF #PPLNBSL4FSWJDF
$PVQPO4FSWJDF &YQFSJNFOUBM4FSWJDF $PVQPO3FQPTJUPSZ &YQFSJNFOUBM3FQPTJUPSZ υϝΠϯϞσϧ ҹґଘؔͷ͖ υϝΠϯϞσϧ -
՝ɿෆ҆ఆͳίʔυ͕ϝΠϯϥΠϯʹ߹ྲྀ 51 #PPLNBSL6TFDBTF #PPLNBSL3FQPTJUPSZ υϝΠϯϞσϧ $PVQPO6TFDBTF &YQFSJNFOUBM6TFDBTF #PPLNBSL4FSWJDF
$PVQPO4FSWJDF &YQFSJNFOUBM4FSWJDF $PVQPO3FQPTJUPSZ &YQFSJNFOUBM3FQPTJUPSZ υϝΠϯϞσϧ ҹґଘؔͷ͖ υϝΠϯϞσϧ -
52 #PPLNBSL6TFDBTF #PPLNBSL3FQPTJUPSZ υϝΠϯϞσϧ $PVQPO6TFDBTF &YQFSJNFOUBM6TFDBTF #PPLNBSL4FSWJDF $PVQPO4FSWJDF
&YQFSJNFOUBM4FSWJDF $PVQPO3FQPTJUPSZ &YQFSJNFOUBM3FQPTJUPSZ υϝΠϯϞσϧ ҹґଘؔͷ͖ υϝΠϯϞσϧ TRYɿෆ҆ఆͳίʔυґଘͰ͖ΔΫϥε ɹɹ ϝιουΛςετʹΑΓ੍ݶ͢Δ -
TRYɿෆ҆ఆͳίʔυґଘͰ͖ΔΫϥε ɹɹ ϝιουΛςετʹΑΓ੍ݶ͢Δ 53 @ArchTest val `ExperimentalΞϊςʔγϣϯ͕༩͞ΕͨΫϥε͕ ExperimentalΞϊςʔγϣϯ͕༩͞ΕͨΫϥε͔ΒͷΈґଘ͞Ε͍ͯΔ͜ͱΛ֬ೝ` =
classes() .that() .areAnnotatedWith(Experimental::class.java) .should(ArchConditions.onlyHaveDependentClassesThat(ANNOTATED_CLASSES)) @ArchTest val `ExperimentalΞϊςʔγϣϯ͕༩͞Εͨϝιου͕ ExperimentalΞϊςʔγϣϯ͕༩͞ΕͨϝιουΫϥε͔ΒͷΈґଘ͞Ε͍ͯΔ͜ͱΛ֬ೝ` = methods() .that() .areAnnotatedWith(TEExperimental::class.java) .should(DEPENDENT_ONLY_ON_ANNOTATED_CODE) -
TRYɿෆ҆ఆͳίʔυґଘͰ͖ΔΫϥε ɹɹ ϝιουΛςετʹΑΓ੍ݶ͢Δ 54 private fun checkRecursively(input: JavaClass): Boolean
{ return when { // ExperimentalΞϊςʔγϣϯΫϥεʹ͔͠༩͞Ε͍ͯͳ͍ͷͰ // ಗ໊ΫϥεωετͷࢠΫϥεͷ߹࠶ؼతʹΫϥεΛݟʹߦ͘ input.isAnonymousClass || input.isNestedClass -> checkRecursively( input.enclosingClass.get() ) input.isAnnotatedWith(Experimental::class.java) -> true else -> false } } -
56 ΞϓϦέʔγϣϯղੳͷར༻๏ CASE 3 : PRࠩͷӨڹൣғͷݕ CASE 4 : ༷ѲϚοϓͷࣗಈ࡞
CASE 5 : αʔϏεؒґଘؔͷՄࢹԽ CASE 6 : ը໘ભҠਤͷࣗಈੜ -
w ଟ͘ͷΫϥε͔Βґଘ͞Ε͍ͯΔඃґଘ͕ ߴ͍ΫϥεͷΤϯϋϯεϦϑΝΫλͰ ൺֱతมߋͷӨڹൣғ͕େ͖͘ σάϨ͕ൃੜ͢Δ͕֬ߴ͍ w ґଘΫϥεଆͰίʔυ্ͷ͕ࠩͰ ෦༷ͷ҉తͳґଘʹΑͬͯ ༷ͷഁյతมߋͷӨڹΛड͚Δ߹͕͋Δ
58 എܠɿࠩͷӨڹൣғͱσάϨ // Τϯϋϯεલ : ਖ਼ͷΛฦ͢ // Τϯϋϯεޙ : Λฦ͢ public int calculate(int input){ var output = ... return output; } // calculate()͕ਖ਼ͷΛฦ͢ͱ͍͏ // લఏʹґଘͯ͠ॲཧΛ࣮ߦ͢Δϝιου public int useCalculate(){ var result = calculate(10); return ... } -
w 13ͷࠩͷӨڹൣғ σάϨ͕ൃੜ͢Δ Մೳੑ͕͋ΔΫϥε$POUSPMMFSΫϥε ΛػցతʹѲ͍ͨ͠ w *%&ΛͬͯಛఆΫϥεʹର͢Δ ґଘؔͷϦετΞοϓՄೳ͕ͩ
13ͷࠩͷશΫϥεʹର࣮ͯ͠ߦͯ͠ ϝϯόʔʹڞ༗͢Δͷʹख͕͔͔ؒΔ 59 ՝ɿࠩͷӨڹൣғͷࣗಈݕग़ -
w 13ͷࠩͷӨڹൣғ σάϨογϣϯ͕ൃੜ͢ΔՄೳੑ͕ ɹ͋ΔΫϥε$POUSPMMFSΫϥε ͷҰཡදΛ$*্Ͱࣗಈੜͯ͠ 13্ͰѲͰ͖ΔΑ͏ʹͨ͠ w ϝϯόʔಉ࢜ͰӨڹൣғͷೝࣝΛ
ڞ༗͢Δ͜ͱͰςετͰ͍͛ͯͳ͍ σάϨογϣϯʹؾ͖ͮ͘͢͢Δ 60 TRYɿCI্͔ΒӨڹൣғΛࣗಈͰϦετΞοϓ -
w "13ͰEJ⒎͕ൃੜͨ͠Ϋϥε #"ʹґଘ͍ͯ͠ΔΫϥε $#ʹґଘ͍ͯ͠ΔΫϥεɾɾɾ ͷΑ͏ʹґଘΫϥεΛॱʹ୧͍͚ͬͯ EJ⒎ͷӨڹൣғΛચ͍ग़͢͜ͱ͕Մೳ w EJ⒎͕ൃੜͨ͠ϑΝΠϧ໊Λ༩͑Δͱ ӨڹൣғͷϑΝΠϧ໊Λग़ྗ͢ΔΑ͏ͳ
HSBEMFλεΫΛ"SDI6OJUΛ࣮ͬͯ 61 HowɿPRͷdiff͔ΒӨڹൣғΛׂΓग़͢ ›./gradlew scanDependenciesFromGitDiff -
ิɿݕग़Ͱ͖ͳ͍Өڹൣғʹҙ w ੩తͳґଘղܾͰݕग़Ͱ͖ͳ͍έʔεʹҙ͢Δ ϦϑϨΫγϣϯͳͲಈతͳґଘղܾ ΞϓϦέʔγϣϯϑϨʔϜϫʔΫʹؔΘΔมߋ ઃఆϑΝΠϧͷมߋ w ͋͘·ͰӨڹൣғͷѲͷαϙʔτͱͯ͠͏ w
σάϨογϣϯࢭ͕తͳΒ ΑΓྑ͍ઃܭςετͰͦͦσάϨογϣϯ͕ ൃੜ͠ʹ͘͘͢Δͷʹӽͨ͜͠ͱͳ͍ 62 03FJMMZ.FEJB *OD ʮ'VOEBNFOUBMTPG4PGUXBSF"SDIJUFDUVSFʯ -
63 TRYɿӨڹൣғͷςετ͚ͩΛ࣮ߦ͢Δ w 13ͷEJ⒎ͷӨڹൣғ͕ར༻ͯ͠ Өڹൣғͷςετ͚ͩΛ ࣮ߦͤ͞Δ͜ͱՄೳ w ࣮ߦίετ͕ߴ͍ςετΛ ޮΑ࣮͘ߦ͢Δ͜ͱͰ͖Δ
w ςετϐϥϛουΛ ॎʹׂ͢ΔΠϝʔδ 4NBMM .FEJVN -BSHF ςετϐϥϛουΛॎʹׂ͢Δ -
TRYɿίϛοτཤྺͱґଘ͔ؔΒࣗಈੜ w ίϛοτͰEJ⒎͕͋ΔΫϥεʹ ґଘ͍ͯ͠ΔΫϥεΛ୧Ε Ͳ͔͜ͷ"1*ΤϯυϙΠϯτʹඥͮ͘ w ֤ϝϯόʔ͕ͦΕͧΕͷ"1*ʹ ͲΕ͚ͩมߋΛՃ͑ͨ͜ͱ͕ ͋Δ͔ΛԽͰ͖Δ
69 ›./gradlew calculateContributeScore -
Howɿίϛοτཤྺͱґଘ͔ؔΒࣗಈੜ w (JU)VCͷ"1*Λར༻ͯ͠ ֤νʔϜϝϯόʔ͕ࠓ·Ͱͷ13্Ͱ ͲͷΫϥεʹͲͷ͘Β͍ࠩΛ ࡞͖ͬͯͨͷ͔Λूܭ w "SDI6OJUΛͬͯ ֤Ϋϥε͕Ͳͷ"1*ΤϯυϙΠϯτͰ
ར༻͞Ε͍ͯΔ͔ͷґଘؔΛऔಘ 70 ›./gradlew calculateContributeScore -
ิɿ༷ཧղ͕ͲΕ͘Β͍ਖ਼͍͠ͷ͔ʁ w ίϛοτܦݧ͕͋Δͱ͍ͬͯܰඍͳϦϑΝΫλͩͬͨΓͱ ͦΕ΄Ͳ༷ཧղͱϦϯΫͯ͠ͳ͍έʔε͋Δ w ίϛοτ13ͷEJ⒎ͷେ͖͞ಉ࣌ฤूͨ͠ϑΝΠϧͳͲ ߟྀͯ͠ॏΈ͚Λ͢Δͱਫ਼্͕͕Δ͔͠Εͳ͍ w ࠓճͷϢʔεέʔεͰ͋͘·Ͱେ·͔ͳ͑͞
Θ͔ΕΑ͍ͱׂΓ͍ͬͯΔ w DGϖΞϓϩάϥϛϯάɾϞϒϓϩάϥϛϯάɾ҉ͷڞ༗ 72 -
74 എܠɿAPIͷΞʔΩςΫνϟʔ (࠶) w ΞϓϦͷϢʔεέʔε୯ҐͰ࡞ͨ͠#''ͷ"1* σʔλͷϦιʔε୯ҐͰ࡞ͨ͠#BDLFOEͷ"1*͔Β ඞཁͳใΛूͯ͠ΞϓϦʹఏڙ #BDLFOE "1* GPS403
#''"1* GPS40& J04"OESPJE ΞϓϦ %#ݕࡧΤϯδϯ A C A B C B -
w "SDI6OJUΛ$PSF"1*Λͬͯ ϝιου͕ݺͼग़͢ϝιουΛ औಘ͢Δ͜ͱ͕Ͱ͖Δ w ͜ΕΛར༻ͯ͠ ͋Δ"1*ΤϯυϙΠϯτ͕ σʔλΛऔಘ͢Δ·Ͱͷ ϝιουγʔέϯεΛ
ίʔυ͔Β͏͜ͱ͕Ͱ͖Δ 78 HowɿΤϯυϙΠϯτͱ֎෦σʔλͷରԠΛ ϝιουͷґଘ͔ؔΒऔಘ -
w ύοέʔδΫϥεͷ ϨΠϠʔຖͷ໋໊نଇΛ ߟྀ͢ΕΑΓޮతʹ γʔέϯεΛ࡞Մೳ w ϥϜμؔ,PUMJO+7.͕ ͲΜͳόΠτίʔυʹͳΔ͔ एׯߟྀ͢Δඞཁ͕͋Δ߹
͋Δ͜ͱʹҙ 79 HowɿΤϯυϙΠϯτͱ֎෦σʔλͷରԠΛ ϝιουͷґଘ͔ؔΒऔಘ $POUSPMMFSΫϥε 3FQPTJUPSZΫϥε 6TF$BTFΫϥε υϝΠϯϞσϧ -
80 @RestController @RequestMapping(value = "/hair/hair-colors") @AllArgsConstructor public class HairColorController
{ private final HairColorUsecase usecase; @GetMapping public ResponseEntity<GetHairColorResponse> getHairColor() { final var body = usecase.getHairColors(); return ResponseEntity.body(body); } } w "1*ΤϯυϙΠϯτ IBJSIBJSDPMPST(&5 )BJS$PMPS6TFDBTFΫϥεͷ HFU)BJS$PMPSTϝιουʹ ґଘ͍ͯ͠Δ w Ξϊςʔγϣϯͷ͔Β "1*ΤϯυϙΠϯτͷύε ϝιουऔಘͰ͖Δ HowɿΤϯυϙΠϯτͱ֎෦σʔλͷରԠΛ ϝιουͷґଘ͔ؔΒऔಘ -
81 @RestController @RequestMapping(value = "/hair/hair-colors") @AllArgsConstructor public class HairColorController
{ private final HairColorUsecase usecase; @GetMapping public ResponseEntity<GetHairColorResponse> getHairColor() { final var body = usecase.getHairColors(); return ResponseEntity.body(body); } } w "1*ΤϯυϙΠϯτ IBJSIBJSDPMPST(&5 )BJS$PMPS6TFDBTFΫϥεͷ HFU)BJS$PMPSTϝιουʹ ґଘ͍ͯ͠Δ HowɿΤϯυϙΠϯτͱ֎෦σʔλͷରԠΛ ϝιουͷґଘ͔ؔΒऔಘ -
82 @Usecase @AllArgsConstructor public class HairColorUsecase { private final
HairColorRepository repository; @Nonnull public GetHairColorResponse getHairColors() { final var hairColors = repository.findAll(); final var hairColorDtos = hairColors.stream() .map(HairColorDtoMapper::map) .collect(Collectors.toList()); return GetHairColorResponse.builder() .hairColors(hairColorDtos) .build(); } } w )BJS$PMPS6TFDBTFΫϥεͷ HFU)BJS$PMPSTϝιου )BJS$PMPS3FQPTJUPSZΫϥεͷ pOE"MMϝιουʹґଘ͍ͯ͠Δ HowɿΤϯυϙΠϯτͱ֎෦σʔλͷରԠΛ ϝιουͷґଘ͔ؔΒऔಘ -
83 @Usecase @AllArgsConstructor public class HairColorUsecase { private final
HairColorRepository repository; @Nonnull public GetHairColorResponse getHairColors() { final var hairColors = repository.findAll(); final var hairColorDtos = hairColors.stream() .map(HairColorDtoMapper::map) .collect(Collectors.toList()); return GetHairColorResponse.builder() .hairColors(hairColorDtos) .build(); } } w )BJS$PMPS6TFDBTFΫϥεͷ HFU)BJS$PMPSTϝιου )BJS$PMPS3FQPTJUPSZΫϥεͷ pOE"MMϝιουʹґଘ͍ͯ͠Δ HowɿΤϯυϙΠϯτͱ֎෦σʔλͷରԠΛ ϝιουͷґଘ͔ؔΒऔಘ -
95 Howɿը໘ΫϥεͷґଘؔΛऔಘ w "OESPJEΞϓϦͷ "DUJDJUZɾ'SBHNFOUΫϥεͷ ݺͼग़͠ͳͲͷґଘؔΛར༻ͯ͠ Ϣʔβʔ͕Ͳͷը໘͔ΒͲͷը໘ʹ ભҠ͠ಘΔͷ͔ͷભҠάϥϑΛ࡞ w ը໘ભҠάϥϑΛը૾ͱͯ͠ग़ྗ
private fun onClickBlog() { val intent = KireiBlogListTabActivity.intent( this, salonId ) startActivity(intent) } private fun navigateToLogin() { val intent = LoginActivity.intent(this) startActivityForResult( intent, REQUEST_CODE_LOGIN ) } -
99 ΞϓϦέʔγϣϯղੳͷར༻๏ CASE 3 : PRࠩͷӨڹൣғͷݕ CASE 4 : ༷ѲϚοϓͷࣗಈ࡞
CASE 5 : αʔϏεؒґଘؔͷՄࢹԽ CASE 6 : ը໘ભҠਤͷࣗಈੜ -
w IUUQTXXXBSDIVOJUPSH w IUUQTNFEJVNDPNYFCJBGSBODFFOGPSDJOHBSDIJUFDUVSFEFDJTJPOT XJUIBSDIVOJUECGDGB w IUUQTCMPHTPSBDMFDPNPUOKQVOJUUFTUZPVSBSDIJUFDUVSFXJUI BSDIVOJUKB w IUUQTBTNPXJP
w IUUQTHJUIVCDPN5/("SDI6OJU&YBNQMFT ࢀߟURL : 1 101