Created
August 25, 2015 13:15
-
-
Save jakeouellette/739305ae54e7730e94d6 to your computer and use it in GitHub Desktop.
Cohesive Software Engineering Talk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends ./layout.jade | |
// This talk requires a combination of reveal.js, Jade, and some style sheets I made to support it. | |
block slides | |
section | |
strong Highly Cohesive Software Programming | |
section | |
p let's pretend you're a software engineer | |
// Fragments are revealed on slide next | |
p.fragment Senior Jawa Developer | |
+notes. | |
Let's pretend you're a software engineer | |
Let's say I have a mythical language called Jawa, | |
section | |
// Dimafter fragments are hidden after being shown | |
p.fragment.dimafter new feature | |
section | |
p.fragment.dimafter a service to upload translations to ci | |
p.fragment ci-3po | |
section | |
p.fragment.dimafter awesome. | |
p.fragment.dimafter Your teammate just finished a tool to convert resources into d3 visualizations in jawa | |
p.fragment r2d3 | |
+notes. | |
and they push it up | |
section | |
p let's make them work together | |
img.fragment.noborder(src='resources/c3po_r2d2_1.gif') | |
p.fragment oh dear | |
+notes. | |
It turns out, r-2-d3 and ci-3po were both mutating the same file resources | |
you see, r2d3 was built unextensibly | |
section | |
p They're tangled together | |
div.fragment | |
img.noborder(src='resources/r2po.png') | |
+notes. | |
you have to hack them apart | |
section | |
p Programming can be like wiring up a switchboard | |
img(src='resources/switchboard.jpg', height='300', class='noborder') | |
section | |
img(src='resources/switchboard_3.jpg', height='300', class='noborder') | |
+notes. | |
If two people really far away keep calling each other, it'd get pretty tiring to wire them up | |
section | |
img(src='resources/switchboard_2.jpg', height='300', class='noborder') | |
+notes. | |
And can you imagine, if every time you had to call someone, you had to call another operator? | |
section | |
p `Cohesion`: Nearby things are related | |
section | |
p `Tight Coupling`: Unrelated things cannot be separated | |
section | |
p Cohesion is a spectrum | |
p.fragment has 2 sides, light and dark | |
+notes. | |
Extensibility is a spectrum. | |
You can go all in on extensibility, or you can make | |
it impossible for others to play in your walled garden | |
section | |
p High Cohesion | |
// replaceafter fragments are hidden and removed from the css (so the next element is shown where it was) | |
p.fragment.replaceafter Enables MVC | |
p.fragment.replaceafter Minimizes changes to code | |
p.fragment.replaceafter Makes things easier to test | |
p.fragment Enables Separation of Concerns | |
section | |
p High Cohesion: Make things easy to understand | |
p.fragment.replaceafter | |
img.noborder(src='resources/xkcd_star_wars.png', width='900') | |
em source: http://xkcd.com/657/ | |
p.fragment | |
img.noborder(src='resources/xkcd_primer.png', width='900') | |
em source: http://xkcd.com/657/ | |
+notes | |
p star wars may seem complicated, | |
p but really, it has many characters that | |
p come together occasionally, and then have their own story | |
p compared to primer, with very few characters, is nearly incomprehensible | |
p a movie, about time travel | |
section | |
p Overview | |
ul | |
li.fragment.dimafter "object oriented" programming | |
li.fragment.dimafter cohesiveness? | |
+notes. | |
section | |
p What is object oriented Programming, anyway? | |
section | |
p OO, the great pancea? | |
section | |
Most people misunderstand what it is | |
section | |
Most schools start out with something like this | |
+codeexample | |
| class Point { | |
| final int x, y | |
| | |
| public int getX() { return x } | |
| ... | |
section | |
p lisp | |
+codeexample | |
| | |
| (lambda () x) | |
| | |
+notes. | |
Not too different from a lambda function in a functional language | |
section | |
+codeexample | |
| | |
| (lambda (var) | |
| (cond (= var "x") | |
| x | |
| y)) | |
| | |
+notes. | |
Ok, maybe one that determines if it should return x or y | |
section | |
p C | |
+codeexample | |
| | |
| point->x | |
| | |
section | |
p ok, does mutation help? | |
+codeexample | |
| class Point { | |
| int x, y | |
| | |
| public void setX(int x) { this.x = x } | |
| ... | |
+notes. | |
Easily represent as a struct in a lower order language | |
If no mutation | |
section | |
p C | |
p point->x = 5 | |
section | |
p Besides: Immutability ain't needed | |
+codeexample | |
| class Point { | |
| int x, y | |
| | |
| public Point setX(int newX) { | |
| new Point(newX, y) | |
| } | |
| ... | |
+notes. | |
Value objects, transformed into themself | |
section | |
+codeexample | |
| class IntPoint implements Point { | |
| int x, y | |
| | |
| public int getX() { return x } | |
| ... | |
+notes. | |
Ok, what about when you start representing abstractions? | |
section | |
+codeexample | |
| class DoublePoint implements Point { | |
| double x, y | |
| | |
| public int getX() { return (int)x } | |
| ... | |
+notes. | |
Now you can use the same APIs with different underlying representation | |
section | |
p A good example? | |
+notes. | |
is that really a good example though? In this case it's just | |
data that is varying, not behavior | |
section | |
+codeexample | |
| class Image implements Drawable { | |
| int currentFrame = ... | |
| | |
| public void draw(Graphics g) { ... } | |
| ... | |
+notes. | |
Let's use a behavioral abstraction | |
section | |
+codeexample | |
| class Gif implements Drawable { | |
| int currentFrame = ... | |
| | |
| public void draw(Graphics g) { ... } | |
| ... | |
+notes. | |
Now you can represent new abstractions | |
section | |
An object is a first-class, dynamically dispatched behavior. | |
+notes. | |
http://wcook.blogspot.com/2012/07/proposal-for-simplified-modern.html?m=1 | |
dynamic dispatch in C: http://www.cs.rit.edu/~ats/books/ooc.pdf | |
http://lwn.net/Articles/444910/ | |
section | |
p Dynamic dispatch is sometimes called | |
ul | |
li.fragment.dimafter "message passing" | |
li.fragment.dimafter "late binding" | |
li.fragment.dimafter "dynamic binding" | |
li.fragment.dimafter "polymorphism" | |
+notes. | |
Polymorphism is what java devs are used to hearing | |
section | |
p Optional, but #[strong not essential:] | |
ul | |
li.fragment mutable state | |
li.fragment inheritance | |
li.fragment classes | |
li.fragment.fragment identity | |
section | |
p Mutable state | |
+notes. | |
Compose a program of value objects, state changes | |
through creation of new objects | |
section | |
+codeexample | |
| class Gif implements Drawable { | |
| int currentFrame = ... | |
| | |
+codeblock | |
| public Pair<Frame, Drawable> draw() { | |
| return new Pair( | |
| getFrame(), | |
| new Gif(currentFrame++)); | |
| } | |
| ... | |
+notes. | |
We can create the same Gif by returning | |
a frame of the image, no mutation required | |
section | |
p inheritance | |
+notes. | |
composition can be isometric | |
Go: Dynamic dispatch | |
In java, inheritance is a means to achieve dispatch, so essential, | |
but extends is not: | |
section | |
+codeexample | |
| interface Drawable { | |
| public void draw(Graphics g); | |
| } | |
section | |
Others might pass around that thing | |
+codeexample | |
| public void setDrawable(Drawable d); | |
+notes. | |
Must be something that extends that type | |
section | |
p Inherits interface, can be used as type: | |
+codeexample | |
| public class Gif implements Drawable { | |
| public void draw(Graphics g) { | |
| | |
section | |
p Does not inherit interface, can't be used | |
+codeexample | |
| public class Artist { | |
| public void draw(Graphics g); | |
| | |
+notes. | |
Artist doesn't extend the interface, so can't be used. | |
You could wrap this artist, but there's a lot of downsides there | |
section | |
p Go: Interfaces are protocols | |
+codeexample | |
| type Drawable interface { | |
| Draw() | |
| } | |
p | |
em anything with Draw can be used by anything needing a Drawable | |
+notes. | |
Languages like go can define new protocols / interfaces | |
Existing types didn't inherit them explicitly. | |
section | |
p you don't need inheritance in all languages to do polymorphism | |
section | |
p identity, e.g., == | |
+notes. | |
Compare by reference | |
section | |
p a.id.equals(b.id) | |
+notes. | |
If this is needed for objects, can be added to specific implementation | |
if things are using this explicitly, it is tightly coupling and preventing | |
dynamic dispatch on equals, so may cause issues. | |
section | |
p Objects are useful | |
p.fragment If they have state, they don't expose it | |
section | |
p Use objects to achieve high level conceptual programming. | |
p.fragment.replaceafter Law of demeter | |
p.fragment Principle of Least Surprise | |
section | |
p Don't do things to my data, ask me to do things. | |
section | |
p Law of demeter: | |
p.fragment when one wants a dog to walk, | |
p.fragment one does not command the dog's legs to walk directly; | |
p.fragment instead one commands the dog which then commands its own legs. | |
section | |
p A method may only call things in direct scope | |
section | |
p So let's say this circle represents an object with a method | |
img(src='resources/lod_1.png', height='300', class='noborder') | |
section | |
p It can call a method of it's own class | |
img(src='resources/lod_2.png', height='300', class='noborder') | |
+codeexample | |
| this.draw() | |
section | |
p Or methods of objects injected in | |
img(src='resources/lod_4.png', height='300', class='noborder') | |
+codeexample | |
| graphics.draw() | |
section | |
p Or methods of objects it made | |
img(src='resources/lod_5.png', height='300', class='noborder') | |
+codeexample | |
| local = new Gif() | |
| local.draw() | |
section | |
p Or methods of objects in it's class | |
img(src='resources/lod_3.png', height='300', class='noborder') | |
+codeexample | |
| this.helper.draw() | |
section | |
p If you can do it anyway, why am I doing it for you? | |
section | |
p Law of demeter is about #[em behavior] of objects | |
section | |
p A lot of data is hierarchical | |
+codeexample | |
| "person": [ | |
| "head": [ "hair": "brown", "eyes": "blue" ], | |
| "legs": 2] | |
section | |
p Can be encoded as objects | |
+codeexample | |
| person = new Person( | |
| new Head(new Hair("brown"), new Eyes("blue")), | |
| new Legs(2), ...) | |
section | |
p Does this violate law of demeter? | |
+codeexample | |
| person.hair.color | |
section | |
p Here, person is just immutable data-structure | |
section | |
p Not the same "badness" | |
section | |
+codeexample | |
| public class PersonPainter implements Drawable { | |
| public PersonPainter(Person p) | |
+notes. | |
One benefit of immutable abstractions is it makes | |
parameter lists simpler | |
section | |
+codeexample | |
| public class PersonPainter implements Drawable { | |
| public PersonPainter(Hair h, Legs l, ...) | |
+notes. | |
Could be passing in each individual component. | |
section | |
p violating the law | |
+codeexample | |
| public class View { | |
| public void paint() { | |
+codeblock | |
| if (personPainter.getPerson() | |
| .hair().equals("brown") { | |
| // Vary paint style | |
| } | |
+notes. | |
What happens when a higher level class reaches into | |
a bheavior object's inner state? | |
section | |
p With Inheritance: Easy to add new classes | |
+codeexample | |
| public class PersonPainter implements Drawable { | |
| public void draw(Graphics g) { | |
| ... | |
| } | |
section | |
+codeexample | |
| public class DogPainter implements Drawable { | |
| public void draw(Graphics g) { | |
| ... | |
| } | |
section | |
p New classes for same behaviors through extends | |
div(style="background:#fff;") | |
img(src='resources/new_class.gif', height='150', class='noborder') | |
section | |
p Let's say you want to add a behavior | |
+codeexample | |
| | |
| public void drawOutline(Graphics g) { | |
| | |
section | |
+codeexample | |
| interface Drawable { | |
| public void draw(Graphics g); | |
+codeblock | |
| public void drawOutline(Graphics g); | |
| } | |
section | |
+codeexample | |
| public class PersonPainter implements Drawable { | |
| public void draw(Graphics g) { | |
| ... | |
| } | |
| | |
+codeblock | |
| | |
| public void drawOutline(Graphics g) { | |
| ... | |
section | |
div(style="background:#fff;") | |
img(src='resources/new_function.gif', height='150', class='noborder') | |
section | |
p Could pull "Painting" behavior into single class: | |
+codeexample | |
| public class Painter { | |
| | |
| public void draw(Object o) { | |
| if (o instanceof Person) { | |
| ... | |
| } | |
| ... | |
| } | |
section | |
+codeexample | |
| public class Painter { | |
| | |
| public void draw(Object o) { | |
| if (o instanceof Person) { | |
| ... | |
| } | |
| } | |
+codeblock | |
| public void drawOutline(Object o) { | |
| if (o instanceof Person) { | |
| ... | |
| } | |
| ... | |
| } | |
section | |
p But then, the only way to extend types is to add to every behavior | |
+codeexample | |
| public class Painter { | |
| | |
| public drawOutline(Object o) { | |
| if (o instanceof Person) { | |
| ... | |
| } | |
| | |
| if (o instanceof Dog) { | |
| ... | |
| } | |
+codeblock | |
| if (o instanceof Cat) { | |
| ... | |
| } | |
| ... | |
section | |
div(style="background:#fff;") | |
img(src='resources/new_class.gif', height='300', class='noborder') | |
section | |
p this is known as the `expression problem` | |
section | |
p There are solutions | |
div.fragment(style="background:#fff;") | |
p Clojure Protocols | |
img(src='resources/protocols.gif', height='300', class='noborder') | |
section | |
p Keep data safe: Don't conflate "Person" with "Painting a Person" | |
p One is data, the other is behavior | |
section | |
p Person was just data, wouldn't ask person to paint itself | |
section | |
p In Java, must make the tradeoff between: | |
ul | |
li.fragment easy type extension | |
li.fragment easy behavior extension | |
section | |
p AVOID mixing concepts: | |
+codeexample | |
| public class PersonPainter implements Drawable { | |
| public void draw(Graphics g) { | |
| ... | |
| } | |
| | |
| public Color getHair() { | |
section | |
p So that's Object Orientation | |
section | |
p What leads to High Cohesion? | |
section | |
p Using SOLID | |
p | |
em Martin Fowler | |
ul.fragment | |
li.fragment.dimafter Single Responsibility | |
li.fragment.dimafter Open/Closed | |
li.fragment.dimafter Liskov Substitution | |
li.fragment.dimafter Interface Segregation | |
li.fragment Dependency Inversion | |
+notes. | |
Martin fowler, prominent OO proponent | |
the solid princples are | |
section | |
p not the only advocate | |
section | |
p unextensible code is unmockable | |
+notes. | |
you see, it's hard to write tests if your code is unextensible | |
section | |
p Google Testability Team Testable Software Principles | |
p | |
em Jonathan Wolter, Russ Ruffer, Miško Hevery | |
ul.fragment | |
li.fragment.dimafter Class does too much | |
li.fragment.dimafter Reaching into collaborators | |
li.fragment.dimafter Has Singletons | |
li.fragment Constructors do real work | |
+notes. | |
section | |
h2 Holub on Patterns | |
p | |
em Allen Holub | |
ul.fragment | |
li.fragment.dimafter Extends is evil | |
li.fragment.dimafter Getters and Setters are evil | |
p | |
img.fragment(src='resources/emperor_palpatine.gif', height='300', class='noborder') | |
+notes. | |
Allen holub promonent JavaWorld, Dr. Dobbs | |
Consultant | |
section | |
p good code | |
+notes. | |
All these share characteristics, some are | |
guidelines to avoid, some are guidelines to do | |
section | |
p examples | |
+notes. | |
I am going to argue there are 2 core Principles | |
by which all others can be derived, let's understand | |
what makes code inflexible through example. | |
section | |
+codeexample | |
p | |
| class R2D3 { | |
| public visualize() { | |
| ... | |
| } | |
| } | |
+notes. | |
Let's talk about R2D3. | |
We said it visualizes resources using d3 | |
a javascript framework | |
section | |
+codeexample | |
p | |
| class R2D3 { | |
| | |
+codeblock | |
| public visualize(String projectRoot) { | |
| List<File> resources = getResources(projectRoot) | |
| // Visualizer code | |
| } | |
| | |
+codeblock | |
| private List<File> getResources(String projectRoot) { | |
| ... | |
| } | |
| } | |
div.fragment | |
p | |
em multiple-responsibility | |
+notes | |
p so this visualize task | |
p calls this getResources method | |
section | |
p getResources is irreplacable, but irrelevant | |
section | |
p Unmockable. | |
section | |
p Testing requires knowledge of internals :( | |
+codeexample | |
| | |
| PowerMock.createPartialMock(R2D3.class, | |
| "getResources").andReturn(files); | |
| | |
+notes. | |
To change the behavior in unit test, you'd have to mock this get resources | |
method. | |
section | |
p Powermock requires looking at internals | |
img(src='resources/c3po_naked.jpg', height='400', class='noborder') | |
+notes. | |
a hidden collaborator | |
section | |
p No Mocks in production | |
img(src='resources/mock_programmer.gif', height='450', class='noborder') | |
+notes. | |
not like we can use them to replace behaviors. | |
section | |
p Implies an iceberg | |
img(src='resources/iceberg.jpg', height='400', class='noborder') | |
+notes. | |
The need to powermock implies a tight coupling on internal behavior, not | |
a single responsibility | |
Not taking advantage of OO facilities to separate behaviors | |
section | |
p API exists, why not make it explicit? | |
section | |
p what if I extend the thing | |
+codeexample | |
+codeblock | |
| class R2D3 extends FileFinder { | |
| | |
| public visualize(String projectRoot) { | |
+codeblock | |
| List<File> resources = getResources(projectRoot) | |
| // Visualizer code | |
| } | |
| } | |
div.fragment | |
p | |
em multiple-responsibility through | |
p | |
em extends | |
+notes. | |
the problem is this method is tightly coupled | |
to this class's implementation of get resources | |
section | |
+codeexample | |
| | |
| PowerMock.createPartialMock(FileFinder.class, | |
| "getResources").andReturn(files); | |
| | |
+notes. | |
Still need to mock out the behavior in test, and | |
if that internal class references something mock-worthy, but it, itself, isnt, | |
then you get into tree-hierarchy searches that are complicated | |
section | |
p Hard to Swap | |
img(src='resources/swap_board.gif', height='500', class='noborder') | |
+notes. | |
hard to change behaviors, hard to swap out | |
section | |
p ok i'll do it earlier | |
+codeexample | |
p | |
| class R2D3 { | |
| List<File> files; | |
| | |
+codeblock | |
| R2d3(String projectRoot) { | |
| ResourceService finder = | |
| new FileFinder(projectRoot) | |
| files = finder.getResources() | |
| } | |
| | |
| public visualize() { | |
| ... | |
| } | |
| } | |
div.fragment | |
p | |
em multiple-responsibility through | |
p | |
em constructor coupling | |
+notes | |
p OK, we can extract FileFinder out | |
p create it ahead of time in our constructor | |
section | |
p Testing requires knowledge of internals :( | |
+codeexample | |
| | |
| expectNew(FileFinder.class, "projectRoot") | |
| .andReturn(files); | |
| | |
+notes. | |
Now, in order to change the behavior you have to mock out internals. | |
section | |
h2 avoid new in constructors | |
p.fragment dependency inversion | |
section | |
p ok, no new in my constructor!!!1 | |
+codeexample | |
p | |
| class R2D3 { | |
| List<File> files; | |
| | |
| R2d3(String projectRoot) { | |
+codeblock | |
| files = FileFinder.getResources(projectRoot) | |
| } | |
| } | |
div.fragment | |
p | |
em multiple-responsibility through | |
p | |
em Singleton / global coupling | |
+notes. | |
OK | |
section | |
p Testing requires knowledge of internals :( | |
+codeexample | |
| | |
| PowerMock.mockStaticPartial( | |
| FileFinder.class, "getResources"); | |
| EasyMock.expect(FileFinder.getResources(...)) | |
| .andReturn(files); | |
| | |
+notes. | |
Still mocking internals | |
section | |
p When do statics improve code? | |
p.fragment.dimafter no this | |
p.fragment.dimafter pure functions | |
p.fragment.dimafter Still is coupling | |
+notes. | |
Avoid bleeding local scope. Handy for pure functions, good no intent to override | |
section | |
+codeexample | |
| class R2D4 extends R2D3 { | |
| | |
| @override | |
| public List<File> getResources() | |
+notes. | |
One of the nice things about statics is they prevent inheritance | |
from being used to replace collaborator behaviors | |
section | |
p perfect code | |
+codeexample | |
| class R2D3 { | |
| final List<File> files; | |
| | |
+codeblock | |
| R2d3(List<File> visualizableFiles) { | |
| this.files = visualizableFiles; | |
| } | |
| | |
+codeblock | |
| public visualize() { | |
| // visualization code | |
| } | |
| } | |
+notes. | |
A good pattern is to separate | |
out the concerns and JUST pass in the files | |
you might also inject it into the | |
visualize method depending on how it works | |
section | |
p perfect is the enemy of good | |
+codeexample | |
p | |
| class R2D3 { | |
| List<File> files; | |
| | |
+codeblock | |
| public static R2D3 create(String projectRoot) { | |
| ResourceService finder = | |
| new FileFinder(projectRoot) | |
| files = finder.getResources() | |
| return new R2D3(files) | |
| } | |
| | |
+codeblock | |
| R2d3(List<File> visualizableFiles) { | |
| this.files = visualizableFiles; | |
| } | |
| ... | |
| } | |
+notes. | |
You can make incremental refactorings that | |
get you closer to ideal. Better than passing | |
factories everywhere | |
section | |
p so that's 'single responsibility' | |
section | |
p Ci3po: the translator | |
+codeexample | |
| class Ci3po { | |
| ... | |
| public translate() { | |
| ... | |
| } | |
| } | |
section | |
+codeexample | |
| class Ci3po { | |
| ... | |
| public translate(TaskContainer t) { | |
+codeblock | |
| List<File> files = t.getByPath(:app:fileFinder").fileFinder.getResources(); | |
| ... | |
| } | |
| } | |
div.fragment | |
p | |
em tightly coupled inputs | |
+notes. | |
This is tightly coupled, to mock it: | |
section | |
p Annoying to mock. | |
section | |
+codeexample | |
| Ci3po.translate(new TaskContainer() { | |
+codeblock | |
| public getByPath(String s) { | |
| return resourceService | |
| } | |
+codeblock | |
| ... (40 more lines of code) | |
| }) | |
+notes. | |
If we want to mock just the internal service, we have to | |
mock the full api, including the 40-some odd methods that are unused | |
section | |
p Could have been easier. | |
section | |
+codeexample | |
| Ci3po.translate(resourceService.getResources()) | |
section | |
p Cognitive load | |
img(src='resources/confused_1.gif', height='500', class='noborder') | |
+notes. | |
The cognitive load to understand mirrors the actual API usage | |
section | |
+codeexample | |
| interface ResourceService | |
| public List<File> getResources() | |
p vs | |
div.fragment | |
+codeexample | |
| interface ResourceService | |
| public List<File> getResources() | |
| public void delete() | |
| public File find(String path) | |
+notes. | |
if a class doesn't need elements of an interface | |
let's not make it more complex because of them | |
section | |
p Inevitably: | |
+codeexample | |
| class YourResourceService extends ResourceService | |
| public delete() { | |
| throw new UnsupportedOperationException("nooo!") | |
| } | |
| ... | |
div.fragment | |
p | |
em tight coupled inputs through | |
p | |
em extreme liskov substitution | |
section | |
+codeexample | |
| interface ResourceService | |
| public List<File> getResources() | |
div.fragment | |
+codeexample | |
| class FileFinder | |
| public List<File> getResources() { | |
| ... | |
+codeblock | |
| public void deleteFiles() { | |
| ... | |
+notes | |
p Let's assume you've got a interface, resource service | |
p and a class that implements it | |
p maybe that class has a lot of other methods | |
p that are weird | |
section | |
+codeexample | |
| class Ci3po { | |
| ... | |
+codeblock | |
| public translate(FileFinder f) { | |
| List<File> = f.getResources() | |
| ... | |
| } | |
| } | |
div.fragment | |
p | |
em tightly coupled inputs through | |
p | |
em overly specific implementation | |
+notes. | |
Now I'm locked into creating a FileFinder, | |
if it has a lot of extra scope, I don't | |
want to have to build that state. | |
Let me pass in what you'll use! | |
section | |
p I would have to extend a real implementation. | |
+codeexample | |
| ci3po.translate(new FileFinder() {...}); | |
section | |
+codeexample | |
| class Ci3po { | |
| ... | |
| public translate(ResourceService f) { | |
+codeblock | |
| if (f instanceof FileFinder) { | |
| List<File> files = (FileFinder) f.getResources() | |
| ... | |
| } | |
| } | |
| } | |
div.fragment | |
p | |
em tightly coupled inputs through | |
p | |
em closed for extension code | |
+notes. | |
I'm STILL locked into creating a FileFinder, | |
because you are using instanceof | |
section | |
h2 instanceof only on datatypes | |
p.fragment further reading: scala case classes | |
+notes. | |
because: if its not immutable data, can ask it to solve problem | |
for you. | |
section | |
p Ci3po: Translations as a service | |
+codeexample | |
| class Ci3po { | |
| | |
+codeblock | |
| Uploader uploader; | |
| | |
| public translate(ResourceService f) { | |
| ... | |
| } | |
| } | |
+notes. | |
Ok, now let's say Ci3po has an uploader it uses | |
section | |
+codeexample | |
| class Ci3po { | |
| | |
| Uploader uploader; | |
| | |
| public translate(ResourceService f) { | |
+codeblock | |
| uploader.setEndpoint('http://ci/translations') | |
| uploader.sendGet( | |
| new HttpGet('hasTranslations', | |
| params(f.getFiles())) | |
| ... | |
| } | |
| } | |
div.fragment | |
p | |
em tightly coupled inputs through | |
p | |
em reaching into state | |
+notes. | |
Translator calls into an uploader, instead of asking it | |
if it has translations, it makes a request, coupling to httpclient | |
section | |
p better | |
+codeexample | |
| uploader.hasTranslations(files) | |
section | |
h2 Core Principles | |
ol | |
li.fragment Classes should have a single responsibility | |
li.fragment Classes should not have visibility into scope they don't need. | |
section | |
p Real world applications | |
section | |
p per-ide onboarding instructions | |
div(style="background:#777;") | |
img(src='resources/refactoring_1_needs.png', class='noborder') | |
+notes. | |
We used to build the IDE onboarding instructions per IDE. | |
Every time a new dev needed to add onboarding instructions, they had | |
to change intellij and eclipse | |
The problem was, there was a difference between the data "what needed to be onboarded", | |
and the process of onboarding | |
section | |
p Refactored | |
div(style="background:#777;") | |
img(src='resources/refactoring_1.png', class='noborder') | |
+notes. | |
We pulled the IDE onboarding instructions out, they were just data | |
now each IDE responds to data independently | |
section | |
p view-owning-controller | |
div(style="background:#777;") | |
img(src='resources/refactoring_2.png', class='noborder') | |
+notes. | |
Another example: our view owned our controller, any time we wanted | |
to change navigation, we were manipulating classes that were constructing jlabels | |
When we need the ability for people to jump into a context for migration, | |
the code for this got really gross, so we made each view dumb. | |
Views only respond to and generate events and show sub-views. They don't transition | |
to new states. This is heirarchical, so subviews are dumb, but the higher level view | |
might have some controller logic | |
section | |
p dumb-views | |
div(style="background:#777;") | |
img(src='resources/refactoring_2_needs.png', class='noborder') | |
section | |
p Conclusions | |
ul | |
li.fragment "object oriented" | |
li.fragment cohesion -- best practices | |
section | |
p thank you | |
img(src='resources/c3po_r2d2_2.gif', height='400', class='noborder') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment