ãã¹ãã³ãŒããšã¯äœã§ããïŒ ãããæžãã«ã¯ã©ã®ãããªã«ãŒã«ã«åŸãã¹ãã§ããïŒ ã³ãŒãããŒã¹ã®æºåãæŽã£ãŠããªãå Žåããã®ãããªã³ãŒãã®èšè¿°ãéå§ããã«ã¯ã©ãããã°ããã§ããïŒ
ãµã³ã¯ãããã«ãã«ã¯ã§éå¬ããã
Mobius 2017ã«ã³ãã¡ã¬ã³ã¹ã§ã®Antonã®ãã¬ãŒã³ããŒã·ã§ã³ã«åºã¥ãããå€ãã®ã³ãŒãäŸãšã€ã©ã¹ããå«ãèšäºã Antonã¯Junoã®Androidã¢ããªã±ãŒã·ã§ã³éçºè
ã§ããã圌ã®ä»äºã§ã¯å€ãã®é¢é£æè¡ã«è§ŠããŠããŸãã ãã®ã¬ããŒãã¯Androidã«é¢ãããã®ã§ã¯ãªããKotlinã«é¢ãããã®ã§ã¯ãªããäžè¬çãªãã¹ãããã©ãããã©ãŒã äžããã³èšèªäžã«ãããããããã³ã³ããã¹ãã«é©å¿ã§ããã¢ã€ãã¢ã«é¢ãããã®ã§ãã
ãªããã¹ããå¿
èŠãªã®ã§ããïŒ
ãŸããã³ãŒãã®ãã¹ããäœæããçç±ãŸãã¯ãã¹ããäœæããçç±ã決å®ãã䟡å€ããããŸãã ããã€ãã®çç±ãèããããŸãã
- ã³ãŒããä¿¡é Œããããã
- ããã¥ã¡ã³ãã®æºåã®ããã
- ãªãã¡ã¯ã¿ãªã³ã°åŸããã£ããç ããŸãã
- ã³ãŒããããéãæžãããã
- ååã«èªæ
¢ããã
ãããŠãããããæãéèŠãªçç±ã¯
ããããžã§ã¯ããé·æã«ããã£ãŠçããŠçºå±ã§ãããšããããšã§ãïŒã€ãŸããå€æŽïŒã éçºãšã¯ãæ°ããæ©èœã®è¿œå ããã°ã®ä¿®æ£ããªãã¡ã¯ã¿ãªã³ã°ãæå³ããŸãã
ã³ãŒãããŒã¹ã倧ãããªããšãããŒã¹ãè€éã«ãªãããããã¹ãããå¯èœæ§ãé«ããªããŸãã ãããŠã圌女ãçç£ã«å
¥ããšããšã©ãŒã®ä»£åãé«ããªããŸãã ãã®çµæãå€ãã®å Žåãå€æŽãéåžžã«å°é£ã§ããã察åŠãéåžžã«å°é£ã§ãã
é·åœãããžã§ã¯ããäœæãããšãã«è§£æ±ºãã2ã€ã®ã°ããŒãã«ã¿ã¹ã¯ã次ã«ç€ºããŸãã
- ã·ã¹ãã ã®è€éããã€ãŸãç¹å®ã®ããžãã¹èŠä»¶ã«å¿ããŠã·ã¹ãã ãã§ããã ãã·ã³ãã«ã«ããæ¹æ³ã管çããŸãã
- ã·ã¹ãã ãã¹ãïŒä»æ¥ãããã«ã€ããŠè©±ããŠããïŒã
ãã¹ãã³ãŒããšã¯äœã§ããïŒ
ãã¹ããæžã蟌ãããšãããšäœãããŸããããªãã®ã§ããããïŒ å€ãã®å Žåãã·ã¹ãã ã¯ããã«å¯Ÿå¿ã§ããŠããŸããã é£æ¥ããããŒããšéåžžã«é¢é£ããŠãããããå
¥åãã©ã¡ãŒã¿ãŒãèšå®ããŠãã¹ãŠãæ£ããæ©èœããããšã確èªããããšã¯ã§ããŸããã

ãã®ãããªç¶æ³ãåé¿ããã«ã¯ãã³ãŒããæ£ããèšè¿°ãããã€ãŸããã¹ãå¯èœã«ããå¿
èŠããããŸãã
ãã¹ãã³ãŒããšã¯äœã§ããïŒ ãã®è³ªåã«çããã«ã¯ããŸããã¹ããšã¯äœããç解ããå¿
èŠããããŸãã ãã¹ããå¿
èŠãªã·ã¹ãã ããããšããŸãããïŒSUT-System Under TestïŒã ãã¹ããšã¯ãããã€ãã®å
¥åããŒã¿ã®è»¢éãšãäºæ³ãããçµæã«å¯Ÿããçµæã®æ€èšŒã§ãã ãã¹ãã³ãŒãã¯ãå
¥åããã³åºåãã©ã¡ãŒã¿ãŒãå®å
šã«å¶åŸ¡ã§ããããšãæå³ããŸãã

ãã¹ãã³ãŒããèšè¿°ããããã®3ã€ã®ã«ãŒã«
ã³ãŒãããã¹ãå¯èœã«ããã«ã¯ã3ã€ã®èŠåã«åŸãããšãéèŠã§ãã ãããã®ãããããäŸã§è©³ããèŠãŠã¿ãŸãããã
èŠå1.åŒæ°ãæž¡ããå€ãæ瀺çã«æž¡ããŸãã
é¢æ°ïŒNåã®åŒæ°ãåããç¹å®ã®æ°ã®å€ãè¿ãããã¥ãŒã å
ã®ç¹å®ã®é¢æ°ïŒã®ãã¹ããèŠãŠã¿ãŸãããã
f(Arg[1], ... , Arg[N]) -Ë (R[1], ... , R[L])
ãããŠããããã§ã¯ãªãé¢æ°ããããŸãïŒ
fun nextItemDescription(prefix: String): String { GLOBAL_VARIABLE++ return "$prefix: $GLOBAL_VARIABLE" }
ã©ã®å
¥åãããã«ããããæ€èšããŠãã ããã ãŸããåŒæ°ãšããŠæž¡ããããã¬ãã£ãã¯ã¹ã ãŸããé¢æ°ã®çµæã«ã圱é¿ãããããå
¥åã¯ã°ããŒãã«å€æ°ã®å€ã§ãã é¢æ°ã®çµæã¯æ»ãå€ïŒæååïŒã§ãããã°ããŒãã«å€æ°ã®å¢å ã§ãã ãããåºå£ã§ãã
æŠç¥çã«ã¯ã次ã®å³ã®ããã«ãªããŸãã

å
¥åïŒæ瀺çããã³æé»çïŒãšåºåïŒæ瀺çããã³æé»çïŒããããŸãã ãã®ãããªé¢æ°ããçŽç²ãªé¢æ°ãäœæããã«ã¯ãæé»çãªå
¥åãšåºåãåé€ããå¿
èŠããããŸãã ãã®å Žåãå¶åŸ¡ãããæ¹æ³ã§ãã¹ããããŸãã ããšãã°ã次ã®ããã«ïŒ
fun ItemDescription(prefix: String, itemIndex: Int): String { return "$prefix: $itemIndex" }
èšãæãããšãé¢æ°ã¯ããã®ãã¹ãŠã®å
¥åãšåºåãæ瀺çã«æž¡ããããã©ãããã€ãŸãåŒæ°ãšæ»ãå€ã®ãªã¹ããä»ããŠæž¡ããããã©ãããç°¡åã«ãã¹ãã§ããŸãã
ã«ãŒã«2.äŸåé¢ä¿ãæ瀺çã«æž¡ã
2çªç®ã®ã«ãŒã«ãç解ããããã«ãã¢ãžã¥ãŒã«ãé¢æ°ãšããŠèããããšããå§ãããŸãã ã¢ãžã¥ãŒã«ã¯ãåŒã³åºããæéã®çµéãšãšãã«å»¶é·ãããé¢æ°ã§ãããšä»®å®ããŸããã€ãŸããå
¥åãã©ã¡ãŒã¿ãŒã®äžéšã¯ããæç¹ã§è»¢éãããäžéš-次ã®è¡ã§ãäžéš-ã¿ã€ã ã¢ãŠãåŸãä»ã®äžéšãªã©ã«è»¢éãããŸãã .dã ãããŠãåºå£ã«ã€ããŠãåãã§ãïŒä»ãäžéš-å°ãåŸã§ããªã©ã
M(In[1], ... , In[N]) -Ë (Out[1], ... , Out[L])
ãã®ãããªã¢ãžã¥ãŒã«é¢æ°ã®å
¥åãšåºåã¯ã©ã®ããã«èŠããŸããïŒ æåã«ã³ãŒããèŠãŠãããããäžè¬çãªå³ãäœæããŠã¿ãŸãããã
class Module( val title: String
ãã®ãããªã¯ã©ã¹ã®ã³ã³ã¹ãã©ã¯ã¿ãŒãåŒã³åºããšããäºå®ã¯ãé¢æ°ã®å
¥åã§ãããæååãåºåã«æž¡ãããšã¯æããã«å
¥åã§ããããŸãã çµæã¯ã¡ãœãããåŒã³åºããããã©ããã«äŸåãããããã¯ã©ã¹ã®ã¡ãœãããåŒã³åºããšããäºå®ãé¢æ°ã®å
¥åã«ãªããŸãã
class Module( val title: String
æ瀺çãªäŸåé¢ä¿ããå€ãååŸããããšãå
¥åã§ãã 䜿çšåã«ã¢ãžã¥ãŒã«APIãä»ããŠæž¡ãããå ŽåãäŸåé¢ä¿ãæ瀺çã«åŒã³åºããŸãã
class Module( val title: String // input val dependency: Explicit // dependency ){ fun doSomething() { // input val explicit = dependency.getCurrentState() //input // ⊠} }
æé»çãªäŸåé¢ä¿ããå
¥åãååŸããããšãå
¥åã§ãã
class Module( val title: String // input val dependency: Explicit // dependency ){ fun doSomething() { // input val explicit = dependency.getCurrentState() //input val implicit = Implicit.getCurrentState() //input // ⊠} }
åºå£ã«é²ã¿ãŸãããã ãã£ãŒã«ãããç¹å®ã®å€ãè¿ããããšãæãåºããŸãã å€éšãã確èªã§ããããããã®å€ã®å€æŽã¯é¢æ°ã®åºåã§ãã
class Module( ){ var state = "Some state" fun doSomething() { state = "New state"
äžéšã®å€éšç¶æ
ã®å€æŽãåºåã§ãã 次ã®ããã«æ瀺çã«ããããšãã§ããŸãã
class Module( val dependency: Explicit // dependency ){ var state = "Some State" fun doSomething() { state = "New State"
ãŸãã¯ãæé»çã«ã次ã®ããã«ïŒ
class Module( val dependency: Explicit // dependency ){ var state = "Some state" fun doSomething() { state = "New state" // output dependency.setCurrentState("New state") //output Implicit.setCurrentState("New state") //input // ⊠} }
ãŸãšããŸãããã
In[1], ⊠, In[N]
ãã®ãããªæ±çšã¢ãžã¥ãŒã«ã®å
¥åã¯æ¬¡ã®ãšããã§ãã
- ã¢ãžã¥ãŒã«APIããã³ãã®äŸåé¢ä¿ã®APIãšã®çžäºäœçšã
- ãããã§äŒããæå³ã
- ãããã®çžäºäœçšãè¡ã£ãé åºã
- ãããã®çžäºäœçšã®éã®æéã
åºåãšã»ãŒåãïŒ
Out[1], ⊠, Out[N]
æ±çšã¢ãžã¥ãŒã«ã®åºåã¯æ¬¡ã®ãšããã§ãã
- ã¢ãžã¥ãŒã«APIããã³ãã®äŸåé¢ä¿ã®APIãšã®çžäºäœçšã
- ãããã§äŒããæå³ã
- ãããã®çžäºäœçšãè¡ã£ãé åºã
- ãããã®çžäºäœçšã®éã®æéã
- ç£èŠå¯èœãªã¢ãžã¥ãŒã«ã®ç¹å®ã®ç¶æ
ã®å€æŽã¯ãå€éšããååŸãããŸãã
ãã®æ¹æ³ã§ã¢ãžã¥ãŒã«ãå®çŸ©ãããšãã¢ãžã¥ãŒã«ã®ãã¹ãããã»ã¹ãã€ãŸããã®ã¢ãžã¥ãŒã«ã§èšè¿°ããããã¹ãã¯ããã®é¢æ°ã®åŒã³åºããšçµæã®æ€èšŒã§ããããšãããããŸãã ã€ãŸããgivenãããã¯ãšwhenãããã¯ïŒgivenãšwhenã¢ãããŒã·ã§ã³ã䜿çšããå ŽåïŒã«èšè¿°ããã®ã¯ãé¢æ°ãåŒã³åºãããã»ã¹ã§ããã次ã«çµæãæ€èšŒããããã»ã¹ã§ãã

ãããã£ãŠãã¢ãžã¥ãŒã«ã®ãã¹ãŠã®å
¥åãšåºåããã¢ãžã¥ãŒã«APIãŸãã¯æ瀺çãªäŸåé¢ä¿ã®APIãä»ããŠéä¿¡ãããå Žåãã¢ãžã¥ãŒã«ã®ãã¹ãã容æã«ãªããŸãã
M(In[1], ... , In[N]) -Ë (Out[1], ... , Out[L])
ã«ãŒã«3.ãã¹ãã§äŸåé¢ä¿ã®äºææ§ãå¶åŸ¡ãã
æ瀺çãªåŒæ°ãšæ瀺çãªäŸåé¢ä¿ããã£ãŠããç§ãã¡ã¯ãŸã å®å
šã«å¶åŸ¡ããããšã¯ã§ããŸããããã®ããã§ãã
ããšãã°ãã¢ãžã¥ãŒã«ã«ã¯æ瀺çãªäŸåé¢ä¿ããããŸãã ã¢ãžã¥ãŒã«ã¯äœãããã3åããŠããããã£ãŒã«ãã«æžã蟌ã¿ãŸãã
class Module(explicit: Explicit) { val tripled = 3 * explicit.getValue() }
ãã®ããã®ãã¹ããäœæããŸãã
class Module(explicit: Explicit) { val tripled = 3 * explicit.getValue() } @Test fun testValueGetsTripled() { }
ã©ãããããããã¢ãžã¥ãŒã«ãæºåããããããTripledãã£ãŒã«ãã®å€ãååŸããçµæã«æžã蟌ã¿ã15ã§ããããšãæåŸ
ãã15ãçµæãšçããããšã確èªããŸãã
class Module(explicit: Explicit) { val tripled = 3 * explicit.getValue() } @Test fun testValueGetsTripled() {
æ倧ã®è³ªåã¯ãäžäœ5ã€ãè¿ããçµæãšããŠ15ãååŸããå¿
èŠããããšèšãããã«ãæ瀺çãªäŸåé¢ä¿ãã©ã®ããã«æºåãããã§ãã æ瀺çãªäŸåé¢ä¿ã«å€§ããäŸåããŸãã
æãããªäŸåé¢ä¿ãã·ã³ã°ã«ãã³ã§ããå Žåããã¹ãã§ã¯ã5ã€ãè¿ãïŒããšèšãããšã¯ã§ããŸãããã³ãŒãã¯ãã§ã«èšè¿°ãããŠããããããã¹ãã§ã³ãŒããå€æŽããããšã¯ã§ããŸããã
// 'object' stands for Singleton in Kotlin object Explicit { fun getValue(): Int = ... }
ãããã£ãŠããã¹ãã¯æ©èœããŸãããéåžžã®äŸåé¢ä¿ãããã«è»¢éããããšã¯ã§ããŸããã
// 'object' stands for Singleton in Kotlin object Explicit { fun getValue(): Int = ... } @Test fun testValueGetsTripled() { val result = Module( ??? ).tripled val expected = 15 assertThat(result).isEqualTo(expected) }
finalã¯ã©ã¹ã§ãåãã§ã-ãããã®åäœãå€æŽããããšã¯ã§ããŸããã
// Classes are final by default in Kotlin Class Explicit { fun getValue(): Int = ... } @Test fun testValueGetsTripled() { val result = Module( ??? ).tripled val expected = 15 assertThat(result).isEqualTo(expected) }
æåŸã®é©åãªã±ãŒã¹ã¯ãæ瀺çãªäŸåé¢ä¿ãäœããã®å®è£
ãæã€ã€ã³ã¿ãŒãã§ã€ã¹ã§ããå Žåã§ãã
interface Explicit { fun getValue(): Int class Impl: Explicit { override fun getValue(): Int = ... } }
次ã«ããã¹ãã§ãã®ã€ã³ã¿ãŒãã§ã€ã¹ãæ¢ã«æºåããäžäœ5ã€ãè¿ããã¹ãå®è£
ãäœæããæåŸã«ã¢ãžã¥ãŒã«ã¯ã©ã¹ã«æž¡ããŠãã¹ããå®è¡ããŸãã
@Test fun testValueGetsTripled() { val mockedExplicit = object : Explicit { override fun getValue(): Int = 5 } val result = Module(mockedExplicit).tripled val expected = 15 assertThat(result).isEqualTo(expected) }
é¢æ°ã¯ãã©ã€ããŒããªå ŽåããããŸããããã§ã¯ããã©ã€ããŒããªå®è£
ãšã¯äœãã調ã¹ãæé»çãªäŸåé¢ä¿ããªãããšãã·ã³ã°ã«ããŒã³ãæé»çãªå Žæããã¯äœãåŸãããªãããšã確èªããå¿
èŠããããŸãã ãããŠãååãšããŠããããªãã¯APIãä»ããã³ãŒãã®ãã¹ãã«åé¡ã¯ãªãã¯ãã§ãã ã€ãŸãããããªãã¯APIãå
¥åãšåºåãå®å
šã«èšè¿°ããŠããå ŽåïŒä»ã«ãªãå ŽåïŒããããªãã¯APIã¯äºå®äžååã§ãã
ãã¹ãå¯èœãªã³ãŒããå®éã«æžãããã®3ã€ã®ã«ãŒã«
ããçš®ã®ã¢ãŒããã¯ãã£ã®ãªããã¹ãæžã¿ã³ãŒããæ³åããã®ã¯é£ããã®ã§ãäŸãšããŠMVPã䜿çšããŸãã ãŠãŒã¶ãŒã€ã³ã¿ãŒãã§ã€ã¹ããã¥ãŒã¬ã€ã€ãŒãããžãã¹ããžãã¯ãæ¡ä»¶ä»ãã§åéãããã¢ãã«ããã©ãããã©ãŒã ã¢ããã¿ãŒã®ã¬ã€ã€ãŒïŒAPIããã¢ãã«ãåé¢ããããã«èšèšãããŠããïŒããŠãŒã¶ãŒã€ã³ã¿ãŒãã§ã€ã¹ã«é¢é£ä»ããããŠããªããã©ãããã©ãŒã APIããã³ãµãŒãããŒãã£APIããããŸãã

ã¢ãã«ãšãã¬ãŒã³ã¿ãŒã¯ãã©ãããã©ãŒã ããå®å
šã«åé¢ãããŠãããããããã§ãã¹ãããŠããŸãã
æ瀺çãªå
¥åãšåºåã¯äœã§ãã
ã¯ã©ã¹ãšããŸããŸãªæ瀺çãªå
¥åããããŸããå
¥åæååãã©ã ããObservableãã¡ãœããåŒã³åºããããã³æ瀺çãªäŸåé¢ä¿ãéããŠè¡ãããåãããšã§ãã
class ModuleInputs( input: String, inputLambda: () -> String, inputObservable: Observable<String>, dependency: Explicit ) { private val someField = dependency.getInput() fun passInput(input: String) { } }
åºå£ã®ç¶æ³ã¯éåžžã«äŒŒãŠããŸãã åºåã¯ãã¡ãœããããäœããã®å€ãè¿ããã©ã ããObservableãããã³æ瀺çãªäŸåé¢ä¿ãä»ããŠäœããã®å€ãè¿ãå ŽåããããŸãã
class ModuleOutputs( outputLambda: (String) -> Unit, dependency: Explicit ) { val outputObservable = Observable.just("Output") fun getOutput(): String = "Output" init{ outputLambda("Output") dependency.passOutput("Output") } }
æé»çãªå
¥åãšåºåã¯ã©ã®ããã«èŠããããããæ瀺çã«å€æããæ¹æ³
æé»çãªå
¥åããã³åºåã¯æ¬¡ã®ãšããã§ãã
- ã·ã³ã°ã«ãã³
- ä¹±æ°ãžã§ãã¬ãŒã¿ãŒ
- ãã¡ã€ã«ã·ã¹ãã ãšãã®ä»ã®ã¹ãã¬ãŒãž
- æé
- æžåŒèšå®ãšãã±ãŒã«
次ã«ãããããã«ã€ããŠè©³ãã説æããŸãã
ã·ã³ã°ã«ãã³
ãã¹ãã§ã·ã³ã°ã«ããŒã³ã®åäœãå€æŽããããšã¯ã§ããŸããã
class Module { private val state = Implicit.getCurrentState() }
ãããã£ãŠããããã¯æ瀺çãªäŸåé¢ä¿ãšããŠåãåºãå¿
èŠããããŸãã
class Module (dependency: Explicit){ private val state = dependency.getCurrentState() }
ä¹±æ°ãžã§ãã¬ãŒã¿ãŒ
以äžã®äŸã§ã¯ãã·ã³ã°ã«ãã³ãåŒã³åºããŸããããã©ã³ãã ã¯ã©ã¹ã®ãªããžã§ã¯ããäœæããŸãã ãããã圌ã¯ããã€ãã®éçã¡ãœãããèªåã®äžã«åŒã£åŒµã£ãŠããŸãããããã¯ãããªãæ¹æ³ïŒããšãã°ãçŸåšã®æéïŒã«ã圱é¿ãäžããããšã¯ã§ããŸããã
class Module { private val fileName = "some-file${Random().nextInt()}" }
ãããã£ãŠãç§ãã¡ãå¶åŸ¡ã§ããªããµãŒãã¹ã¯ãå¶åŸ¡ã§ããã€ã³ã¿ãŒãã§ãŒã¹ã«æž¡ãã®ãçã«ããªã£ãŠããŸãã
class Module(rng: Rng) { private val fileName = "some-file${Random(rng.nextInt()}" }
ãã¡ã€ã«ã·ã¹ãã ãšãã®ä»ã®ã¹ãã¬ãŒãž
ã¹ãã¬ãŒãžãåæåããç¹å®ã®ã¢ãžã¥ãŒã«ããããŸãã 圌ãããããšã¯ãäœããã®æ¹æ³ã§ãã¡ã€ã«ãäœæããããšã ãã§ãã
class Module { fun initStorage(path: String) { File(path).createNewFile() } }
ãã ãããã®APIã¯éåžžã«æœè¡çã§ããæåããå Žåã¯trueãè¿ããåããã¡ã€ã«ãããå Žåã¯falseãè¿ããŸãã ãŸããããšãã°ããã¡ã€ã«ãäœæããã ãã§ãªããç解ããå¿
èŠããããŸãããã¡ã€ã«ãäœæããããã©ãããäœæãããŠããªãå Žåã¯ããã®çç±ã¯äœã§ããã ãããã£ãŠãåä»ããšã©ãŒãäœæãããããåºåã«è¿ããŸãã ãŸãã¯ããšã©ãŒããªãå Žåã¯ãnullãè¿ããŸãã
class Module { fun initStorage(path: String): FileCreationError? { return if (File(path).createNewFile()) { null } else { FileCreationError.Exists } } }
ããã«ããã®APIã¯2ã€ã®äŸå€ãã¹ããŒããŸãã ãããããã£ããããå床ãåä»ããšã©ãŒãè¿ããŸãã
class Module { fun initStorage(path: String): FileCreationError? = try { if (File(path).createNewFile()) { null } else { FileCreationError.Exists } } catch (e: SecurityException) { FileCreationError.Security(e) } catch (e: Exception) { FileCreationError.Other(e) } }
OKãåŠçããŸããã ããã§ãã¹ãããŸãã åé¡ã¯ããã¡ã€ã«ãäœæãããšå¯äœçšãããããšã§ãïŒã€ãŸãããã¡ã€ã«ã·ã¹ãã ã«ãã¡ã€ã«ãäœæããŸãïŒã ãããã£ãŠãäœããã®æ¹æ³ã§ãã¡ã€ã«ã·ã¹ãã ãæºåããããã€ã³ã¿ãŒãã§ã€ã¹ã®èåŸã«å¯äœçšããããã¹ãŠã®ãã®ãäœæããå¿
èŠããããŸãã
class Module (private val fileCreator: FileCreator){ fun initStorage(path: String): FileCreationError? = try { if (fileCreator.createNewFile(path)) { null } else { FileCreationError.Exists } } catch (e: SecurityException) { FileCreationError.Security(e) } catch (e: Exception) { FileCreationError.Other(e) } }
æé
ãããå
¥ãå£ã§ããããšã¯ããã«ã¯æããã§ã¯ãããŸãããããããããã§ããããšã¯ãã§ã«äžã§ããã£ãŠããŸãã
class Module { private val nowTime = System.current.TimeMillis() private val nowDate = Date() // and all other time/date APIs }
ããšãã°ãã¢ãžã¥ãŒã«ã«ã¯30åéåŸ
æ©ããããžãã¯ããããŸãã ãã®ããã®ãã¹ããäœæããäºå®ãããå Žåããã¹ãŠã®ãŠããããã¹ãã¯éåžžââ30åéåæ Œããå¿
èŠãããããããã¹ãã30åéåŸ
æ©ããå¿
èŠã¯ãããŸããã æéãå¶åŸ¡ã§ããããã«ãããã®ã§ãæéã®çµéãšãšãã«ãã¹ãŠã®äœæ¥ãã€ã³ã¿ãŒãã§ã€ã¹ã«ç§»åããŠã·ã¹ãã å
ã®1ã€ã®ãã€ã³ãã«ããããšã¯çã«ããªã£ãŠããŸãã ã 次ã«ãããšãã°ãæèšã移åããå Žåã®ã¢ãžã¥ãŒã«ã®åäœããã¹ãã§ããŸãã
class Module (time: TimeProvider) { private val nowTime = time.nowMillis() private val nowDate = time.nowDate() // and all other time/date APIs }
æžåŒèšå®ãšãã±ãŒã«
ããã¯æãé°æ¹¿ãªæé»ã®å
¥ãå£ã§ãã éåžžã®ãã¬ãŒã³ã¿ãŒãäœããã®çš®é¡ã®ã¿ã€ã ã¹ã¿ã³ããååŸããå³å¯ã«æå®ããããã¿ãŒã³ïŒAMãŸãã¯PMãªããã³ã³ããªãããã¹ãŠãèšå®ãããŠããããã§ãïŒã«åŸã£ãŠãã©ãŒããããããã£ãŒã«ãã«æ ŒçŽãããšããŸãã
class MyTimePresenter(timestamp: Long) { val formattedTimestamp = SimpleDateFormat("yyyy-MM-dd HH:mm").format(timestamp) }
ãã®ããã®ãã¹ããæžããŠããŸãã æžåŒèšå®ããããã©ãŒã ã§ãããäœã§ããããèããããããŸããããã®äžã§ã¢ãžã¥ãŒã«ãå®è¡ãã衚瀺ããããã®ã確èªããããã«æžãçããæ¹ãç°¡åã§ãã
class MyTimePresenter(timestamp: Long) { val formattedTimestamp = SimpleDateFormat("yyyy-MM-dd HH:mm").format(timestamp) } fun test() { val mobiusConfStart = 1492758000L val expected = "" val actual = MyTimePresenter(timestamp).formattedTimeStamp assertThat(actual).isEqualTo(expected) }
ã¡ããŠã¹ã¯4æ21æ¥ã®10æããå§ãŸãã®ãèŠãŸããïŒ
class MyTimePresenter(timestamp: Long) { val formattedTimestamp = SimpleDateFormat("yyyy-MM-dd HH:mm") .format(timestamp) } fun test() { val mobiusConfStart = 1492758000L val expected = "2017-04-21 10:00" val actual = MyTimePresenter(timestamp).formattedTimeStamp assertThat(actual).isEqualTo(expected) }
OKããããããŒã«ã«ãã·ã³ã§å®è¡ãããšããã¹ãŠãæ©èœããŸãã
>> `actual on dev machine` = "2017-04-21 10:00"
CIã§éå§ããäœããã®çç±ã§Mobiusã7ããéå§ããŸãã
>> `actual on CI` = "2017-04-21 07:00"
CIã«ã¯ç°ãªãã¿ã€ã ãŸãŒã³ããããŸãã SimpleDateFormatã¯ããã©ã«ãã®ã¿ã€ã ãŸãŒã³ã䜿çšãããããUTC + 0ã®ã¿ã€ã ãŸãŒã³ã§ãããããã«å¿ããŠãããã§ã®æå»ã®ãã©ãŒããããç°ãªããŸãã ãã¹ãã§ã¯ãGMTããŒãã®CIãµãŒããŒã§ã¯ãããããããåå®çŸ©ããŸããã§ããããå¥ã®æ¹æ³ããããŸãã ãããŠãããã¯ã以äžãå«ããå Žæã«é¢é£ä»ããããŠãããã¹ãŠã®å
¥åãæé»ã®ãã¡ã«ç€ºããŸãã
- é貚
- æ°å€åœ¢åŒ
- ã¿ã€ã ãŸãŒã³
- ãã±ãŒã«
ãã¹ãã§äŸåé¢ä¿ãæ¿¡ããæ¹æ³
圌ãã¯ããéã®åŒŸäžžãã¯ãªããšèšã£ãŠããŸãããç§ã«ã¯ãããã¯moç¬ã«é¢ãããã®ãããããã§ãã ã€ã³ã¿ãŒãã§ãŒã¹ã¯ã©ãã§ãæ©èœããããã§ãã ã€ã³ã¿ãŒãã§ãŒã¹ã®èåŸã«å®è£
ãé ããå Žåãã€ã³ã¿ãŒãã§ãŒã¹ã眮ãæããããããããã¹ãã§ç¢ºå®ã«çœ®ãæããããšãã§ããŸãã
interface MyService { fun doSomething() class Impl(): MyService { override fun doSomething() { } } } class TestService: MyService { override fun doSomething() { } } val mockService = mock<MYService>()
ã€ã³ã¿ãŒãã§ã€ã¹ã¯ãã·ã³ã°ã«ããŒã³ã§äœããããã®ã«åœ¹ç«ã€ããšãããããŸãã ãããžã§ã¯ãã«ã·ã³ã°ã«ãã³ãããããããGodObjectã§ãããšããŸãããã äžåºŠã«è€æ°ã®åå¥ã®ã¢ãžã¥ãŒã«ã«è§£æããããšã¯ã§ããŸããããããçš®ã®DIãããçš®ã®ãã¹ã容ææ§ããã£ãããšå°å
¥ããããšèããŠããŸãã ãããè¡ãã«ã¯ããã®ã·ã³ã°ã«ãã³ã®ãããªãã¯APIãŸãã¯ãããªãã¯APIã®äžéšãç¹°ãè¿ãã€ã³ã¿ãŒãã§ã€ã¹ãäœæããã·ã³ã°ã«ãã³ã«ãã®ã€ã³ã¿ãŒãã§ã€ã¹ãå®è£
ãããŸãã ãŸããã¢ãžã¥ãŒã«ã§ã·ã³ã°ã«ãã³èªäœã䜿çšãã代ããã«ããã®ã€ã³ã¿ãŒãã§ã€ã¹ãæ瀺çãªäŸåé¢ä¿ãšããŠæž¡ãããšãã§ããŸãã ãã¡ãããå€éšã§ã¯åãGetInstanceã®è»¢éã«ãªããŸãããå
éšã§ã¯æ¢ã«ã¯ãªãŒã³ãªã€ã³ã¿ãŒãã§ã€ã¹ã§äœæ¥ããŠããŸãã ããã¯ããã¹ãŠãã¢ãžã¥ãŒã«ãšDIã«æž¡ããããŸã§ã®äžéã¹ãããã§ãã
interface StateProvider { fun getCurrentState(): String } object Implicit: StateProvider { override fun getCurrentState(): String = "State" } class SomeModule(stateProvider: StateProvider) { init { val state = StateProvider.getCurrentState() } }
ãã¡ãããä»ã®éžæè¢ããããŸãã äžã§èšã£ãããã«ãæçµã¯ã©ã¹ãéçã¡ãœãããã·ã³ã°ã«ããŒã³ããŠã§ããã¢ãŠãããããšã¯ã§ããŸããã ãã¡ããããã¥ãŒãããããšãã§ããŸããæçµã¯ã©ã¹ã«ã¯Mockito2ãæçµã¯ã©ã¹ãéçã¡ãœãããã·ã³ã°ã«ããŒã³ã«ã¯PowerM®ckããããŸããããããã«ã¯å€ãã®åé¡ããããŸãã
- ã»ãšãã©ã®å Žåãèšèšäžã®åé¡ãéç¥ããŸãïŒããã¯äž»ã«PowerMockã«é©çšãããŸãïŒ
- ããšãã°ã1501ãã¹ããªã©ã®ããæç¹ã§åäœãåæ¢ããå Žåãããããããã¹ãã«é©ããã¢ãŒããã¯ãã£ãããã«èããŠããã®ãããªãã¬ãŒã ã¯ãŒã¯ã䜿çšããªãæ¹ãããã§ãããã
ãã©ãããã©ãŒã ããã®æœè±¡åãšãããå¿
èŠãªçç±
æœè±¡åã¯ãViewã¬ã€ã€ãŒãšãã©ãããã©ãŒã ã¢ããã¿ãŒã®ã¬ã€ã€ãŒã§è¡ãããŸãã

ã¬ã€ã€ãŒã®æœè±¡åã衚瀺
ãã¥ãŒã¬ã€ã€ãŒã¯ãUIãã¬ãŒã ã¯ãŒã¯ãPresenterã¢ãžã¥ãŒã«ããåé¢ããŸãã äž»ã«2ã€ã®ã¢ãããŒãããããŸãã
- ActivityãViewã€ã³ã¿ãŒãã§ã€ã¹èªäœãå®è£
ããå Žåã
- Viewãå¥ã®ã¯ã©ã¹ã§ããå Žåã
æåã®ãªãã·ã§ã³ãèŠãŠã¿ãŸãããïŒã¢ã¯ãã£ããã£ã¯ãã¥ãŒãå®è£
ããŸãã 次ã«ããã¥ãŒã€ã³ã¿ãŒãã§ã€ã¹ãå
¥åãšããŠåãåãããã®ã¡ãœãããåŒã³åºããããçš®ã®ç°¡åãªãã¬ãŒã³ã¿ãŒããããŸãã
class SplashPresenter(view: SplashView) { init { view.showLoading() } }
ç°¡åãªViewã€ã³ã¿ãŒãã§ãŒã¹ããããŸãïŒ
interface SplashView { fun showLoading() }
ãããŠãã¢ã¯ãã£ããã£ããããonCreateã¡ãœããã®åœ¢åŒã§å
¥åããããSplashViewã€ã³ã¿ãŒãã§ãŒã¹ã®å®è£
ããããŸããããã¯ãäœããã®ããã°ã¬ã¹ã衚瀺ããããã«å¿
èŠãªããšããã©ãããã©ãŒã ã®æ¹æ³ã§æ¢ã«çŽæ¥å®è£
ããŠããŸãã
interface SplashView { fun showLoading() } class SplashActivity: Activity, SplashView { override fun onCreate() { } override fun showLoading() { findViewById(R.id.progress).show() } }
ãããã£ãŠãPresenterã¯æ¬¡ã®ããã«è¡ããŸããOnCreateã§äœæãããããViewãšããŠæž¡ããŸãã å€ãã®å Žåãå®å
šã«æå¹ãªãªãã·ã§ã³ã§ãã
interface SplashView { fun showLoading() } class SplashActivity: Activity, SplashView { override fun onCreate() { SplashPresenter(view = this) } override fun showLoading() { findViewById(R.id.progress).show() } }
2çªç®ã®ãªãã·ã§ã³ããããŸã-å¥ã®ã¯ã©ã¹ãšããŠè¡šç€ºããŸãã ããã§ã¯ãPresenterã¯ãŸã£ããåãã§ãã€ã³ã¿ãŒãã§ã€ã¹ã¯ãŸã£ããåãã§ãããå®è£
ã¯Activityã«é¢é£ããªãå¥åã®ã¯ã©ã¹ã§ãã
class SplashPresenter(view: SplashView) { init { view.showLoading() } interface SplashView { fun showLoading() class Impl : SplashView { override fun showLoading() { } } } }
ãããã£ãŠããã©ãããã©ãŒã ã³ã³ããŒãã³ãã§åäœã§ããããã«ããã©ãããã©ãŒã ãã¥ãŒãå
¥åã§éä¿¡ãããŸãã ãããŠã圌ã¯ãã§ã«åœŒãå¿
èŠãšãããã¹ãŠãããŠããŸãã
interface SplashView { fun showLoading() class Impl(private val viewRoot: View) : SplashView { override fun showLoading() { viewRoot.findViewById(R.id.progress).show() } } }
ãã®å Žåãã¢ã¯ãã£ããã£ã¯ãããã«ãªãããŒããããŸãã ã€ãŸããã€ã³ã¿ãŒãã§ã€ã¹ãçµç¹åãã代ããã«ããã®ãã©ãããã©ãŒã ãã¥ãŒãååŸããŠSplashPresenterãäœæããå¥ã®ã¯ã©ã¹ããã¥ãŒãšããŠäœæããŸãã
class SplashActivity: Activity, SplashView { override fun onCreate() { // Platform View class val rootView: View = ... SplashPresenter( view = SplashView.Impl(rootView) ) } }
å®éããã¹ãã®èŠ³ç¹ããèŠããšãããã2ã€ã®ã¢ãããŒãã¯åãã§ãããªããªããç§ãã¡ã¯ãŸã ã€ã³ã¿ãŒãã§ãŒã¹ããäœæ¥ããŠããããã§ãã ã¢ãã¯ãã¥ãŒãäœæãããããæž¡ããã¬ãŒã³ã¿ãŒãäœæããç¹å®ã®ã¡ãœãããåŒã³åºãããããšã確èªããŸãã
@Test fun testLoadingIsShown() { val mockedView = mock<SplashView>() SplashPresenter(mockedView) verify (mockedView).showLoading() }
å¯äžã®éãã¯ãã¢ã¯ãã£ããã£ããŒã«ãšãã¥ãŒããŒã«ã®è¡šç€ºæ¹æ³ã§ãã ViewããŒã«ãä»ã®ActivityããŒã«ãšæ··ãããªãã»ã©å€§ãããšæãããå Žåã¯ãå¥ã®ã¯ã©ã¹ã«é
眮ããããšããå§ãããŸãã
ãã©ãããã©ãŒã ã©ãããŒã¬ã€ã€ãŒã®æœè±¡å
次ã«ããã©ãããã©ãŒã ã¢ããã¿ãŒã¬ã€ã€ãŒäžã®ãã©ãããã©ãŒã ããã®æœè±¡åã«ã€ããŠèª¬æããŸãã ãã©ãããã©ãŒã ã©ãããŒã¯ãã¢ãã«ã¬ã€ã€ãŒã®åé¢ã§ãã åé¡ã¯ããã©ãããã©ãŒã åŽã®ãã®å±€ã®èåŸã«ãã©ãããã©ãŒã APIãšãµãŒãããŒãã£APIããããããããç°ãªã圢åŒã§æäŸããããããäžè¬çã«ããããå€æŽã§ããªãããšã§ãã ãããã¯ãéçã¡ãœãããã·ã³ã°ã«ããŒã³ãæçµã¯ã©ã¹ãããã³éæçµã¯ã©ã¹ãšããŠæäŸãããŸãã æåã®3ã€ã®ã±ãŒã¹ã§ã¯ãå®è£
ã«åœ±é¿ãäžããããšã¯ã§ããŸãã;ãã¹ãã§ã®åäœã眮ãæããããšã¯ã§ããŸããã ãããŠãããããæçµã¯ã©ã¹ã§ãªãå Žåã«ã®ã¿ããã¹ãã§ã®åäœã«äœããã®åœ¢ã§åœ±é¿ãäžããããšãã§ããŸãã
ãããã£ãŠããã®ãããªAPIãçŽæ¥äœ¿çšãã代ããã«ãã©ãããŒãäœæããã®ãçã«ããªã£ãŠããå ŽåããããŸãã ããã¯ãAPIãçŽæ¥äœ¿çšãããå Žæã§ãã
class Module { init { ThirdParty.doSomething() } }
ãããã代ããã«ããµãŒãããŒãã£ã®APIã®ã¡ãœããã転éããã ãã®æãç°¡åãªã±ãŒã¹ã®ã©ãããŒãäœæããŸãã
interface Wrapper { fun doSomething() class Impl: Wrapper { override fun doSomething() { ThirdParty.doSomething() } } }
å®è£
ã®ã©ãããŒãååŸããã€ã³ã¿ãŒãã§ã€ã¹ã®èåŸã«é ããŸããããããã£ãŠãã¢ãžã¥ãŒã«å
ã§æ¢ã«WrapperãåŒã³åºããŠããŸããããã¯æ瀺çãªäŸåé¢ä¿ãšããŠæäŸãããŸãã
class Module(wrapper: Wrapper) { init { wrapper.doSomething() } }
ä¿èšŒããããã¹ã容ææ§ã«å ããŠãããã¯ä»¥äžãæäŸããŸãã
- ãã©ãããã©ãŒã APIã®èšèšã«ãã€ã³ããã代ããã«ã䟿å©ãªèšèšã䜿çšããæ©èœ
- (Single Responsibility God Object);
- API ( ).
Design Smell, , . , â . , Smell. , , Smell, . .
Android ID- . -, . , , R- .
class SplashPresenter(view: SplashView, resources: Resources) { init { view.setTitle(resources.getString(R.string.welcome)) view.showLoading() } }
â , Android, ID-. , , , :
class SplashPresenter(view: SplashView, resources: Resources) { init { view.setTitle(resources.getString(R.string.welcome)) view.showLoading() } } interface Resources { fun getString(id: Int): String }
, , ID, , . .
public final class R { public static final class string { public static final int welcome=0x7f050000; } }
, , - . iOS Android, .
, . - , .
class SomeModule(input: String) { val state = calculateInitialState(input) // Pure private fun calculateInitialState(input: String): String = "Some complex computation for $input" }
, , , .
class SomeModule(input: String) { val state = calculateInitialState(input) // Pure private fun calculateInitialState(input: String): String = "Some complex computation for $input" } class AnotherModule(input: String) { val state = calculateInitialState(input) // Pure private fun calculateInitialState(input: String): String = "Some complex computation for $input" }
, , - .
class SomeModule(input: String) { val state = calculateInitialState(input) // Pure private fun calculateInitialState(input: String): String = "Some complex computation for $input" } object StateCalculator { fun calculateInitialState(input: String): String = "Some complex computation for $input" }
? calculateInitialState, . , , , .
class SomeModule(input: String, stateCalculator: StateCalculator){ val state = stateCalculator.calculateInitialState(input) } interface StateCalculator { fun calculateInitialState(input: String): String class Impl: StateCalculator { override fun calculateInitialState(input: String): String = "Some complex computation for $input" } }
, , calculateInitialState - . , extension- ( Kotlin), static-, , .
,
, , ( , ). , , , , , .

, .

:
, - (Activity, , broadcast-âŠ), - ( Activity View), , , Presenter. , Presenter, DI.

, .
, ( View), . , ( ).

çµè«ã®ä»£ããã«
: « , , , â ».
. , , . , . , - . , . , .
â ,
Mobius 2017 Moscow :