Win a copy of Spring in Action (5th edition) this week in the Spring forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Bear Bibeault
  • Devaka Cooray
  • Liutauras Vilda
  • Jeanne Boyarsky
Sheriffs:
  • Knute Snortum
  • Junilu Lacar
  • paul wheaton
Saloon Keepers:
  • Ganesh Patekar
  • Frits Walraven
  • Tim Moores
  • Ron McLeod
  • Carey Brown
Bartenders:
  • Stephan van Hulst
  • salvin francis
  • Tim Holloway

Game of Life Lessons: Detecting Code Smells  RSS feed

 
Sheriff
Posts: 12748
210
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:

Junilu Lacar wrote:What kind of higher-level code would require calling the flip(Position) method?


A user interface where you press a cell and it toggles from its current state.


In general, is it a good idea to allow your Presentation layer to dictate the design of your Model?
 
Bartender
Posts: 9494
184
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Sure, if the operation is generic enough that it's not just tailored to the presentation layer.

The flip operation fits in well with the other operations on MutableGeneration. I wouldn't have added it if the code wasn't going to use it, but I don't find it so specific that I can say for sure it will only ever be used in a user interface. Besides, we're not talking about a complete overhaul of the model design. I added a convenience method.

I agree with you that in general the presentation layer shouldn't drive the model design, but it's also not a good idea to hold guidelines as dogma.
 
Stephan van Hulst
Bartender
Posts: 9494
184
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Note also that in practice, often oversights in the model design become apparent only when you start interacting with it through a user interface.
 
Junilu Lacar
Sheriff
Posts: 12748
210
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:I agree with you that in general the presentation layer shouldn't drive the model design, but it's also not a good idea to hold guidelines as dogma.


Agree about dogma but if we look at it objectively and ask ourselves, "Is there a pattern that can help us avoid allowing the presentation from dictating our model's design?"

In searching for the answer, we might discover this: https://en.wikipedia.org/wiki/Presentation%E2%80%93abstraction%E2%80%93control

Since using the UI to set up a GoL seed population only occurs once before you start playing the game (remember, the GoL is defined as a zero-player game), would it make sense to have some abstraction like an InitialSeed instead? The InitialSeed class would be mutable and can certainly have the flip(Position) method in its API. It would also have some kind of method to collect all the data that was set from the UI and pass it to the constructor of an immutable World class.

Yes, this may seem more complex, with an additional InitialSeed class abstraction but wouldn't it also greatly simplify the design of the World class by allowing it to stay immutable?
 
Junilu Lacar
Sheriff
Posts: 12748
210
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:Note also that in practice, often oversights in the model design become apparent only when you start interacting with it through a user interface.



That may be true but at the same time, I would go back to asking the question about allowing the presentation layer to influence the design of the model. I would ask "What is it in the presentation-model relationship that is making me want to make special accommodations for the presentation? What can I do to avoid this and keep the presentation as dumb and as isolated as possible from the model?"

One anti-pattern I've seen in these coderetreats is people getting hung up on input and output. My advice to get them unstuck is to ignore input/output to set up and display the World. When I try to ignore input/output, I usually get a lot farther and the design of my classes tend to be better.

Same goes for my real-world code. I try to adhere to the shape of the testing triangle and keep the number of tests related to UI to a minimum while maximizing the number of unit tests. I avoid test-driving with the UI in mind. When I do that, the UI stuff actually falls into place rather nicely. The converse is not true though, in my experience. It leads to large(r) tests that need to be run from end to end.
 
Stephan van Hulst
Bartender
Posts: 9494
184
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I didn't base my game of life on the assumption that you only set it up once and then let it run until you get bored of it. I based it on the idea that an external force can interact with the world during the time it takes until the next generation evolves. That's part of how I conceived the business model.

If a generation is immutable, then to create the next generation when the previous evolves, the previous generation must either pass all the data to the new generation in its constructor using a mutable type like a Map, or maybe the InitialSeed class you proposed, or it must create an empty instance of the new generation and then modify it through private mutators. I don't find any of these two alternatives clearer than just allowing a generation to be influenced directly. I'm a huge fan of immutable types, but requiring everything to be immutable, when those types don't conceptually represent a "single value", is overdoing it, especially in a language that is not fundamentally functional.

I took a look at the presentation-abstraction-control pattern, and I don't really see how it fits in this context. It also looks like a lot of over-engineering just to avoid the addition of a convenience method, one which doesn't seem that out of place to me in the first place.
 
Junilu Lacar
Sheriff
Posts: 12748
210
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
So the design alternatives would be:

versus

versus other combinations of partial mutability perhaps
 
Stephan van Hulst
Bartender
Posts: 9494
184
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Junilu Lacar wrote:I would ask "What is it in the presentation-model relationship that is making me want to make special accommodations for the presentation? What can I do to avoid this and keep the presentation as dumb and as isolated as possible from the model?"


The presentation IS dumb. It just wants to flip the state of a cell, an action that I would not fundamentally attribute to just presentation code. Instead of implementing the logic itself, it calls a convenience method that does just that. The fact that the method was added in response to this particular use case doesn't mean that the method couldn't be added in response to requirements from the model itself. Again, I don't think flipping a cell is fundamentally presentational.

Let's flip (huehuehue) this around. I could have added the convenience method in at the start, without any code requiring it. I believe it fits in well with the other mutators on the Generation class, it doesn't look like something I added because of the presentation layer (indeed, you asked me why I added it in the first place). However, I left it out due to YAGNI. Then YAGNI wasn't applicable any longer because YNI (you need it). So I added it.

One anti-pattern I've seen in these coderetreats is people getting hung up on input and output. My advice to get them unstuck is to ignore input/output to set up and display the World. When I try to ignore input/output, I usually get a lot farther and the design of my classes tend to be better.


I'm curious about your metric. How do you measure "better"?
 
Stephan van Hulst
Bartender
Posts: 9494
184
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I don't understand the goal of your last code example Junilu. Is it to indicate that convenience methods must be left out? If so, why? If not, then what are you trying to demonstrate?
 
Junilu Lacar
Sheriff
Posts: 12748
210
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:If a generation is immutable, then to create the next generation when the previous evolves, the previous generation must either pass all the data to the new generation in its constructor using a mutable type like a Map, or maybe the InitialSeed class you proposed, or it must create an empty instance of the new generation and then modify it through private mutators.


Ah, but that's not the only way. You'd only need one private mutator, world.clear()

Consider the implications of this game loop:


And maybe PAC isn't the pattern I was thinking of. It's a pattern where presentation deals with different abstractions for input vs. output. When I first saw it, I thought it was a bit much but when I run into things like these that we're discussing, it kind of makes sense to me.

I'll try to implement some of these ideas soon and share the code. We'll see if it does indeed lead to over-engineering. My sense is that as long as I test-drive and refactor ruthlessly along the way, this big picture view can lead me to some interesting design decisions I haven't fully explored before.
 
Junilu Lacar
Sheriff
Posts: 12748
210
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:I'm curious about your metric. How do you measure "better"?


It's pretty nebulous, I admit, but these days it's mostly based on the Four Rules of Simple Design, SOLID, and other principles.

I know that doesn't help much but ¯\_(ツ)_/¯
 
Stephan van Hulst
Bartender
Posts: 9494
184
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Junilu Lacar wrote:Ah, but that's not the only way. You'd only need one private mutator, world.clear()


And do what with it? If that's the only mutator, then you can only ever have empty worlds. If it's the only private mutator, the world isn't immutable.

Consider the implications of this game loop:


Can you elaborate? I'm not sure what point you're trying to make here.
 
Junilu Lacar
Sheriff
Posts: 12748
210
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:Can you elaborate? I'm not sure what point you're trying to make here.


This might get long so buckle up. Caveat: I haven't tested this out, so there may be flaws but let's draw it out here anyway.

A typical implementation of the data structure to track one generation's evolution to the next generation is to have two arrays. Then have current reference on of them and next reference the other.

Then you can do this:

Now, suppose I want to change the implementation and use a couple of List<Location> instead of boolean[][]. What would I need to do? Well, first, I'd probably refactor:

Then I'd strangle the old data structures out.

Yeah, the code gets uglier before it gets prettier but we can still continue to test drive while slowly switching to the new architecture.

I'll continue with another reply.
 
Junilu Lacar
Sheriff
Posts: 12748
210
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
So what would newCalcNextGeneration() look like?

I can imagine a few more iterations of refactoring to where you are making these kinds of calls:

and you'd have a game loop that is perhaps extracted to a GameOfLife class where:

I don't know if you can really consider World as immutable here but it certainly can't be directly manipulated to change individual cell locations.

Of course, now that I look at that, I think it smells, too. It could be better.
 
Junilu Lacar
Sheriff
Posts: 12748
210
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Junilu Lacar wrote:
I can imagine a few more iterations of refactoring to where you are making these kinds of calls:

Of course, now that I look at that, I think it smells, too. It could be better.


This is how I might try to address that smell:

and my thought process might be that a World will have a Population and that Population can evolve on every tick() of the world. I wouldn't necessarily create many Population objects, just as I didn't have to create many arrays, just two. Then I can just swap the references for current and next between every tick() of the world.
 
Junilu Lacar
Sheriff
Posts: 12748
210
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Does this experiment lead to over-engineering? Maybe. Again, I'd have to run through the actual experiment and TDD it. Then I'd try to see how easy it is to change the design with new requirements, like maybe:

1. Allow the game to be displayed on a web page.
2. Allow the game to be displayed on a mobile app
3. Allow a user to browse through a range of generations, going forward and backward. How would you make the most efficient use of memory if you needed to do this?

I'd like to see how easy it is to evolve the design to allow for these capabilities and how cleanly the design can evolve.
 
Junilu Lacar
Sheriff
Posts: 12748
210
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
In fact, I think I'm going to experiment with an implementation that has a flip() method that started this whole sub-discussion in the first place. I'll try to throw away everything I just thought about and start from there and see where refactoring and TDDing leads me.

Thanks for a great discussion so far though.
 
Marshal
Posts: 6263
420
BSD
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The more I look at the design of the code I was working on, the more and more I like it, plus employing an idea which is floating around about passing an initial seed, I think I could get rid of setLivingAt(Location) and setSize(int) altogether from public interface, as they serve only UI and at an initial stage. So basically without these two, the design is completely immutable as cannot be mutated nor directly nor indirectly. The overhead of that is very little I think, because only the ALIVE cells are being tracked.

After adding an initial seed idea that would look like:
 
Junilu Lacar
Sheriff
Posts: 12748
210
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:Can you elaborate? I'm not sure what point you're trying to make here.


Stephan, I'm not entirely sure I clarified what point I was trying to make. But I certainly elaborated a lot, didn't I?

I think maybe it would better to actually code it out before adding more smoke and mirrors to the discussion.

Liu, that last game loop code you posted looks nice. Good job.

Although the loop only dies when the world's population dies out. A world can settle into a fully static state where there is a population but it is no longer evolving. Just want to point that out in case your intent was really to stop the game loop if there were no changes to the population.

On that note, however, a stable world can have a number of isolated oscillators. Have to find a word the clearly expresses that the world is stable. Hmm.... world.isStable() maybe?
 
Saloon Keeper
Posts: 1174
73
Eclipse IDE Hibernate jQuery MySQL Database Spring Tomcat Server
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Congratulations Junilu,

Your question has made it to our Journal    

Have a Cow!
 
Consider Paul's rocket mass heater.
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!