SlideShare a Scribd company logo
REFACTORING

@szafranek
We form a wonderful community.

Yet, we can learn a lot from other programming communities.
REFACTORING
Improving
the design of code
without changing
its external behavior
Refactoring is not...




If the changes affect external behavior, it’s rewriting, not refactoring.
Refactoring is not...




For #1 excuse visit http://xkcd.com/303
continuous
            small steps
Refactoring should be done as a part of our every day workflow.
Don’t wait for massive “big bang” refactorings.
Investment
When to refactor?
It’s an investment:
- Makes change easier.
- Helps find bugs.
But comes with time and effort cost.
enables you to
            go faster

May make little sense just before a deadline.
makes code
               clear to other
                  people

Not machines
Martin Fowler
“Any fool can write code that a computer can understand.
Good programmers write code that humans can understand.”

From the book “Refactoring”:
http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672
;
It’s much more important to write code that will be easy to understand by other people
than just understood by a browser.
clarity
     vs
performance
clarity
          enables
        performance
Some of you have heard that “Premature optimization is the root of all evil”.

It's easier to optimize clean code than to clean up "optimized" code.
CODE SMELLS
Code is like cheese.
Some people find smelly what other find deliciously wonderful.
duplication

It makes change harder.
The rule of 3: the third time you copy something, it’s time to refactor.
long methods

More than 10 lines is long. This rule is arbitrary to force you to stop and think when you
exceed this size.
A method should do one thing!
switch/if
       statements
When they’re long and complex.
speculative
        code
Remove any code that “may be useful later”. You can always recover it from version control.

Very often such code is NEVER nedeed and just confuses people who encounter it.
comments

Comments become bad when used as a deodorant to hide messy code.
STEP #0
jslint
                                                                jshint
Static analysis tools that detect mistakes and enforce clear style.
Using lint was the single most important boost to my productivity in writing JS
John Carmack
“Anything that isn’t crystal clear to a static analysis tool
probably isn’t clear to your fellow programmers”.
From John Carmack’s great article on static analysis tools:
http://altdevblogaday.com/2011/12/24/static-code-analysis/
STEP #1
UNIT TESTS

Tests give a safety net. How else do you know that you didn’t change the behavior?
JavaScript developer’s typical reaction to a question about unit tests
STEP #2

Refactoring catalogue
What you learned about webstandards and clean front-end code is still valid. These are the
basics:
* Separation of concerns.
* Use CSS for styling.
* Don’t inline JavaScript.
* Assemble HTML in templates.
InfoMode.prototype.activate = function (config) {

             this.target = config.target;
             var entity = this.target.entity,
                 entityName = entity.getProperName(),
                 bulk = entity.getInfoModeString(),
                 html = '<h1>' + entityName + '</h1><div class=specification>{bulk}</div>',
                 millisecondsStart, millisecondsTotal, millisecondsEnd, millisecondsLeft;


             if(entity.is('decoration')) {
                 this.target.makeDynamic();
             }

             this.infoPanel.innerHTML = "";


             if(entity.contract) {
                 millisecondsStart = entity.contractStartTime;
                 millisecondsTotal = entity.contract.requiredTime;
                 millisecondsEnd = (new Date()).getTime() - this.target.entity.contractStartTime;
                 millisecondsLeft = millisecondsTotal-millisecondsEnd;

                  if (entity.is('any house')) {
                      if(this.target.entity.contractState === "started") {
                          var gold = this.target.entity.getContractValue();
                          bulk = '<div class="progress"><div style="width: 0%;"></div></div><p>Gives you '+
         gold +
                               ' coins in <span id="timeLeft">'+utils.formatTime(millisecondsLeft)+'</
         span></p>';
                      } else {
                          bulk = bulk || '<p>Please connect to road</p>';
                      }
                  } else if (entity.is('farm')) {
                      var food = this.target.entity.getContractValue();
                      bulk = '<div class="progress"><div style="width: 0%;"></div></div><p>Gives you ' +
         food +
                         ' food in <span id="timeLeft">'+utils.formatTime(millisecondsLeft)+'</span></p>';
                 } else if (entity.is('ship')) {
                     if(this.target.entity.contractState === "started") {
                         bulk = '<div class="progress"><div style="width: 0%;"></div></div><p>Repair
         complete in <span id="timeLeft">' +
                             utils.formatTime(millisecondsLeft) + '</span></p>';
                     }
                 }

            }
            else {
Real   code, one method, ~150 lines.
                if(entity.is('castle')) {

                       if(wooga.castle.playerData.upgradingCastle) {
                           wooga.castle.CastleCompleteScreen.instance().show();
                           this.deactivate();
                           this.manager.setMode(wooga.castle.GameModesManager.Mode.BASIC);
                           return;
                       }

                       bulk = '<p><span class="population">Supported Population: ' +
                           entity.definition.population
};

           if(this.target.entity.contractState === "started") {
               var progress = document.querySelector('.progress div'),
                   timer = document.getElementById('timeLeft');
               progress.style.width = Math.floor((((new Date()).getTime() - millisecondsStart) /
       millisecondsTotal) * 100) + "%";

                 counter = setInterval(function () {
                     var now = new Date().getTime(), timeFromStart = now - millisecondsStart;
                     if((timeFromStart / millisecondsTotal) >= 1) {
                         clearInterval(counter);
                     }
                     timer.innerHTML = utils.formatTime(millisecondsTotal - timeFromStart);
                     progress.style.width = Math.floor((timeFromStart / millisecondsTotal) * 100) + "%";
                 }, 1000);
            }



            utils.publish('view:entity/focused', {
                "entity": entity,
                "entityView": entity.entityView,
                "infoMode": this
            });

            //TODO move to pubsub


            // show boost info in influence area of decoration
            if (this.target instanceof wooga.castle.DecorationView) {
                this.target.entity.game.publish("decoration/showBoost", {
                     entityView: this.target,
                     x: this.target.entity.x,
                     y: this.target.entity.y
                });
            }
            // show boost of houses or farmfields in info mode
            if (this.target instanceof wooga.castle.FarmFieldView ||
                    this.target instanceof wooga.castle.HouseView) {
                if (this.target.entity.boost) {
                    this.target.entity.entityView.showBoostInfo = true;
                }
            }

            this.show();

       };
Real code, one method, ~150 lines.
Extract method
             if(this.target.entity.contractState === "started") {
                 var progress = document.querySelector('.progress div'),
                     timer = document.getElementById('timeLeft');
                 progress.style.width = Math.floor((((new Date()).getTime() -
         millisecondsStart) / millisecondsTotal) * 100) + "%";

                 counter = setInterval(function () {
                     var now = new Date().getTime(), timeFromStart = now - millisecondsStart;
                     if((timeFromStart / millisecondsTotal) >= 1) {
                         clearInterval(counter);
                     }
                     timer.innerHTML = utils.formatTime(millisecondsTotal - timeFromStart);
                     progress.style.width = Math.floor((timeFromStart / millisecondsTotal) *
         100) + "%";
                 }, 1000);
             }




         this.showProgressBar(startTime,requiredTime);


Extract chunk that does one thing into a separate method.
Replace comments

            // show boost info in influence area of decoration

            if (this.target instanceof wooga.castle.DecorationView) {
                this.target.entity.game.publish("decoration/
        showBoost", {
                      entityView: this.target,
                      x: this.target.entity.x,
                      y: this.target.entity.y
                });
            }




        this.showBoosts(entity);

Well-named method makes comment unnecessary.
Remove duplication
if (this.target.entity.is("decoration")) {
    this.target.entity.game.publish("decoration/
showBoost", {
         entityView: this.target,
         x: this.target.entity.x,
         y: this.target.entity.y
    });
}




var entity = this.target.entity;
if (entity.is("decoration")) {
    entity.game.publish("decoration/showBoost", {
         entityView: this.target,
         x: entity.x,
         y: entity.y
    });
}
Replace temp with a query

         var text = entity.getInfoModeString();
         infoPanel.innerHTML = '<div>' + text + '</div>';




         infoPanel.innerHTML =
           '<div>' + entity.getInfoModeString() + '</div>';




If temporary variable is used only once, it doesn’t make sense to introduce it.
Sometimes even two calls don’t justify a temporary variable, unless they’re expensive and you
want to cache the results.
Remove speculative code

switch (action) {
    case "move":
        break;
    case "destroy":
        break;
    default:
        break;
}
Use meaningful names
         var p = this.current.params;
         var r = this.current.results;
         if (r.restProduct) {
             p.query = r.restProduct;
         }




         var params = this.current.params;
         var results = this.current.results;
         if (results.restProduct) {
             params.query = results.restProduct;
         }




There are 2 hard things in CS: “cache invalidation and naming things”.

Leave compression to minifiers!
Use polymorphism
         if (entity.is('house')) {
             if(entity.contractState === "started") {
                 var gold = entity.getContractValue();
                 bulk = '<p>Gives you '+ gold + ' coins</p>';
             } else {
                 bulk = bulk || '<p>Please connect to road</p>';
             }
         } else if (entity.is('farm')) {
             var food = entity.getContractValue();
             bulk = '<p>Gives you ' + food + ' food</p>';
         } else if (entity.is('ship')) {
             bulk = '<p>Repair complete in ' + time + '</span></p>';
         }




         var bulk = entity.getInfoModeString();

Each entity class implements its own version of the method.
this.target = config.target;
               var entity = this.target.entity,
                   entityName = entity.getProperName(),
                   bulk = entity.getInfoModeString(),
                   html = '<h1>' + entityName + '</h1><div class=specification>{bulk}</div>',
                   millisecondsStart, millisecondsTotal, millisecondsEnd, millisecondsLeft;


               if(entity.is('decoration')) {
                   this.target.makeDynamic();
               }

               this.infoPanel.innerHTML = "";


               if(entity.contract) {
                   millisecondsStart = entity.contractStartTime;
                   millisecondsTotal = entity.contract.requiredTime;
                   millisecondsEnd = (new Date()).getTime() - this.target.entity.contractStartTime;
                   millisecondsLeft = millisecondsTotal-millisecondsEnd;

                   if (entity.is('any house')) {
                       if(this.target.entity.contractState === "started") {
                           var gold = this.target.entity.getContractValue();
                           bulk = '<div class="progress"><div style="width: 0%;"></div></div><p>Gives you '+
        gold +
                               ' coins in <span id="timeLeft">'+utils.formatTime(millisecondsLeft)+'</span></
        p>';
                       } else {
                           bulk = bulk || '<p>Please connect to road</p>';
                       }
                   } else if (entity.is('farm')) {
                       var food = this.target.entity.getContractValue();
                       bulk = '<div class="progress"><div style="width: 0%;"></div></div><p>Gives you ' +
        food +                                                                                                  InfoMode.prototype.activate = function (config) {
                        ' food in <span id="timeLeft">'+utils.formatTime(millisecondsLeft)+'</span></p>';           this.target = config.target;
                } else if (entity.is('ship')) {
                    if(this.target.entity.contractState === "started") {
                                                                                                                    var entity = this.target.entity;
                        bulk = '<div class="progress"><div style="width: 0%;"></div></div><p>Repair
        complete in <span id="timeLeft">' +                                                                          if (entity.is('castle') && wooga.castle.playerData.upgradingCastle) {
                            utils.formatTime(millisecondsLeft) + '</span></p>';
                    }
                                                                                                                         wooga.castle.CastleCompleteScreen.instance().show();
                }                                                                                                        this.deactivate();
                                                                                                                         this.manager.setMode(wooga.castle.GameModesManager.Mode.BASIC);
               }
               else {
                                                                                                                         return;
                   if(entity.is('castle')) {                                                                         }

                       if(wooga.castle.playerData.upgradingCastle) {
                           wooga.castle.CastleCompleteScreen.instance().show();
                                                                                                                     if (entity.is('decoration')) {
                           this.deactivate();                                                                            this.target.makeDynamic();
                           this.manager.setMode(wooga.castle.GameModesManager.Mode.BASIC);                           }
                           return;
                       }
                                                                                                                    this.infoPanel.innerHTML = '<h1>' + entity.getProperName() + '</h1><div
                       bulk = '<p><span class="population">Supported Population: ' +                            class=specification>' + entity.getInfoModeString() + '</div>';
                           entity.definition.population
                       + '</span></p>';
                   } else if(entity.is('decoration')) {                                                             // RADAR: [szafranek] Why is this HERE? Moving it to worldView and listening to
                                                                                                                "entity/focused" looks better to me.
                       bulk = '<p>Nearby buildings get a ';
                       var concatBoosts = false;
                                                                                                                    this.manager.view.isFocusedEntityView = function (entityView) {
                       if (entity.definition.goldBoost) {                                                               return entityView === config.target;
                             bulk += 'gold boost of ' + entity.definition.goldBoost + '%';                          };
                             concatBoosts = true;
                       }
                       if (entity.definition.foodBoost) {                                                           if (entity.contractState === wooga.castle.House.ContractState.STARTED) {
                           if(concatBoosts) {                                                                           this.showProgressBar(entity.contractStartTime,
                               bulk += '</p><p>and a ';
                           }
                                                                                                                entity.contract.requiredTime);
                           bulk += 'food boost of ' + entity.definition.foodBoost + '%.';                           } else if (entity.contractState ===
                       }                                                                                        wooga.castle.House.ContractState.CONSTRUCTION) {
                   }
                                                                                                                        this.showProgressBar(entity.constructionStartTime,
               }                                                                                                entity.contract.constructionTime);
                                                                                                                    }
               this.infoPanel.innerHTML = html.replace('{bulk}', bulk);

               this.manager.view.isFocusedEntityView = function (entityView) {                                       utils.publish('view:entity/focused', {
                   return entityView === config.target;                                                                  "entity": entity,
               };
                                                                                                                         "entityView": entity.entityView,
            if(this.target.entity.contractState === "started") {                                                         "infoMode": this
                var progress = document.querySelector('.progress div'),                                              });
                    timer = document.getElementById('timeLeft');
                progress.style.width = Math.floor((((new Date()).getTime() - millisecondsStart) /
        millisecondsTotal) * 100) + "%";                                                                             this.showBoosts(entity);

                   counter = setInterval(function () {
                       var now = new Date().getTime(), timeFromStart = now - millisecondsStart;
                                                                                                                     this.show();
                       if((timeFromStart / millisecondsTotal) >= 1) {
                           clearInterval(counter);                                                              };
                       }
                       timer.innerHTML = utils.formatTime(millisecondsTotal - timeFromStart);
                       progress.style.width = Math.floor((timeFromStart / millisecondsTotal) * 100) + "%";
                   }, 1000);
               }



               utils.publish('view:entity/focused', {
                   "entity": entity,
                   "entityView": entity.entityView,
                   "infoMode": this
               });

               //TODO move to pubsub


               // show boost info in influence area of decoration
               if (this.target instanceof wooga.castle.DecorationView) {
                   this.target.entity.game.publish("decoration/showBoost", {
                        entityView: this.target,
                        x: this.target.entity.x,
                        y: this.target.entity.y
                   });
               }
               // show boost of houses or farmfields in info mode
               if (this.target instanceof wooga.castle.FarmFieldView ||
                       this.target instanceof wooga.castle.HouseView) {
                   if (this.target.entity.boost) {
                       this.target.entity.entityView.showBoostInfo = true;
                   }
               }

               this.show();




Original method after refactoring.
        };
Know where to stop.
Ask yourself: is the further investment wortwhile?
LEARNING MORE
Refactoring
Martin Fowler
coderetreat.org
Organize one yourself if there’s none in your city!
Robert C. Martin
“Learning to write clean code is hard work.
It requires more than just the knowledge of principles and patterns.
You must sweat over it. You must practice it yourself, and watch yourself fail.
You must watch others practice it and fail.”

From the book “Clean Code”:
http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882
Write code for people

Learn from others


@szafranek
_gee_                 fingersss
chez loulou           diathesis
linder6580           Pipistrula
ginza_line         Ocial GDC
superfluity      Łukasz Bałamut
mooong




photo credits

More Related Content

JavaScript Refactoring

  • 2. We form a wonderful community. Yet, we can learn a lot from other programming communities.
  • 4. Improving the design of code without changing its external behavior
  • 5. Refactoring is not... If the changes affect external behavior, it’s rewriting, not refactoring.
  • 6. Refactoring is not... For #1 excuse visit http://xkcd.com/303
  • 7. continuous small steps Refactoring should be done as a part of our every day workflow. Don’t wait for massive “big bang” refactorings.
  • 8. Investment When to refactor? It’s an investment: - Makes change easier. - Helps find bugs. But comes with time and effort cost.
  • 9. enables you to go faster May make little sense just before a deadline.
  • 10. makes code clear to other people Not machines
  • 11. Martin Fowler “Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” From the book “Refactoring”: http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672
  • 12. ; It’s much more important to write code that will be easy to understand by other people than just understood by a browser.
  • 13. clarity vs performance
  • 14. clarity enables performance Some of you have heard that “Premature optimization is the root of all evil”. It's easier to optimize clean code than to clean up "optimized" code.
  • 16. Code is like cheese. Some people find smelly what other find deliciously wonderful.
  • 17. duplication It makes change harder. The rule of 3: the third time you copy something, it’s time to refactor.
  • 18. long methods More than 10 lines is long. This rule is arbitrary to force you to stop and think when you exceed this size. A method should do one thing!
  • 19. switch/if statements When they’re long and complex.
  • 20. speculative code Remove any code that “may be useful later”. You can always recover it from version control. Very often such code is NEVER nedeed and just confuses people who encounter it.
  • 21. comments Comments become bad when used as a deodorant to hide messy code.
  • 23. jslint jshint Static analysis tools that detect mistakes and enforce clear style. Using lint was the single most important boost to my productivity in writing JS
  • 24. John Carmack “Anything that isn’t crystal clear to a static analysis tool probably isn’t clear to your fellow programmers”. From John Carmack’s great article on static analysis tools: http://altdevblogaday.com/2011/12/24/static-code-analysis/
  • 26. UNIT TESTS Tests give a safety net. How else do you know that you didn’t change the behavior?
  • 27. JavaScript developer’s typical reaction to a question about unit tests
  • 29. What you learned about webstandards and clean front-end code is still valid. These are the basics: * Separation of concerns. * Use CSS for styling. * Don’t inline JavaScript. * Assemble HTML in templates.
  • 30. InfoMode.prototype.activate = function (config) { this.target = config.target; var entity = this.target.entity, entityName = entity.getProperName(), bulk = entity.getInfoModeString(), html = '<h1>' + entityName + '</h1><div class=specification>{bulk}</div>', millisecondsStart, millisecondsTotal, millisecondsEnd, millisecondsLeft; if(entity.is('decoration')) { this.target.makeDynamic(); } this.infoPanel.innerHTML = ""; if(entity.contract) { millisecondsStart = entity.contractStartTime; millisecondsTotal = entity.contract.requiredTime; millisecondsEnd = (new Date()).getTime() - this.target.entity.contractStartTime; millisecondsLeft = millisecondsTotal-millisecondsEnd; if (entity.is('any house')) { if(this.target.entity.contractState === "started") { var gold = this.target.entity.getContractValue(); bulk = '<div class="progress"><div style="width: 0%;"></div></div><p>Gives you '+ gold + ' coins in <span id="timeLeft">'+utils.formatTime(millisecondsLeft)+'</ span></p>'; } else { bulk = bulk || '<p>Please connect to road</p>'; } } else if (entity.is('farm')) { var food = this.target.entity.getContractValue(); bulk = '<div class="progress"><div style="width: 0%;"></div></div><p>Gives you ' + food + ' food in <span id="timeLeft">'+utils.formatTime(millisecondsLeft)+'</span></p>'; } else if (entity.is('ship')) { if(this.target.entity.contractState === "started") { bulk = '<div class="progress"><div style="width: 0%;"></div></div><p>Repair complete in <span id="timeLeft">' + utils.formatTime(millisecondsLeft) + '</span></p>'; } } } else { Real code, one method, ~150 lines. if(entity.is('castle')) { if(wooga.castle.playerData.upgradingCastle) { wooga.castle.CastleCompleteScreen.instance().show(); this.deactivate(); this.manager.setMode(wooga.castle.GameModesManager.Mode.BASIC); return; } bulk = '<p><span class="population">Supported Population: ' + entity.definition.population
  • 31. }; if(this.target.entity.contractState === "started") { var progress = document.querySelector('.progress div'), timer = document.getElementById('timeLeft'); progress.style.width = Math.floor((((new Date()).getTime() - millisecondsStart) / millisecondsTotal) * 100) + "%"; counter = setInterval(function () { var now = new Date().getTime(), timeFromStart = now - millisecondsStart; if((timeFromStart / millisecondsTotal) >= 1) { clearInterval(counter); } timer.innerHTML = utils.formatTime(millisecondsTotal - timeFromStart); progress.style.width = Math.floor((timeFromStart / millisecondsTotal) * 100) + "%"; }, 1000); } utils.publish('view:entity/focused', { "entity": entity, "entityView": entity.entityView, "infoMode": this }); //TODO move to pubsub // show boost info in influence area of decoration if (this.target instanceof wooga.castle.DecorationView) { this.target.entity.game.publish("decoration/showBoost", { entityView: this.target, x: this.target.entity.x, y: this.target.entity.y }); } // show boost of houses or farmfields in info mode if (this.target instanceof wooga.castle.FarmFieldView || this.target instanceof wooga.castle.HouseView) { if (this.target.entity.boost) { this.target.entity.entityView.showBoostInfo = true; } } this.show(); }; Real code, one method, ~150 lines.
  • 32. Extract method if(this.target.entity.contractState === "started") { var progress = document.querySelector('.progress div'), timer = document.getElementById('timeLeft'); progress.style.width = Math.floor((((new Date()).getTime() - millisecondsStart) / millisecondsTotal) * 100) + "%"; counter = setInterval(function () { var now = new Date().getTime(), timeFromStart = now - millisecondsStart; if((timeFromStart / millisecondsTotal) >= 1) { clearInterval(counter); } timer.innerHTML = utils.formatTime(millisecondsTotal - timeFromStart); progress.style.width = Math.floor((timeFromStart / millisecondsTotal) * 100) + "%"; }, 1000); } this.showProgressBar(startTime,requiredTime); Extract chunk that does one thing into a separate method.
  • 33. Replace comments // show boost info in influence area of decoration if (this.target instanceof wooga.castle.DecorationView) { this.target.entity.game.publish("decoration/ showBoost", { entityView: this.target, x: this.target.entity.x, y: this.target.entity.y }); } this.showBoosts(entity); Well-named method makes comment unnecessary.
  • 34. Remove duplication if (this.target.entity.is("decoration")) { this.target.entity.game.publish("decoration/ showBoost", { entityView: this.target, x: this.target.entity.x, y: this.target.entity.y }); } var entity = this.target.entity; if (entity.is("decoration")) { entity.game.publish("decoration/showBoost", { entityView: this.target, x: entity.x, y: entity.y }); }
  • 35. Replace temp with a query var text = entity.getInfoModeString(); infoPanel.innerHTML = '<div>' + text + '</div>'; infoPanel.innerHTML = '<div>' + entity.getInfoModeString() + '</div>'; If temporary variable is used only once, it doesn’t make sense to introduce it. Sometimes even two calls don’t justify a temporary variable, unless they’re expensive and you want to cache the results.
  • 36. Remove speculative code switch (action) { case "move": break; case "destroy": break; default: break; }
  • 37. Use meaningful names var p = this.current.params; var r = this.current.results; if (r.restProduct) { p.query = r.restProduct; } var params = this.current.params; var results = this.current.results; if (results.restProduct) { params.query = results.restProduct; } There are 2 hard things in CS: “cache invalidation and naming things”. Leave compression to minifiers!
  • 38. Use polymorphism if (entity.is('house')) { if(entity.contractState === "started") { var gold = entity.getContractValue(); bulk = '<p>Gives you '+ gold + ' coins</p>'; } else { bulk = bulk || '<p>Please connect to road</p>'; } } else if (entity.is('farm')) { var food = entity.getContractValue(); bulk = '<p>Gives you ' + food + ' food</p>'; } else if (entity.is('ship')) { bulk = '<p>Repair complete in ' + time + '</span></p>'; } var bulk = entity.getInfoModeString(); Each entity class implements its own version of the method.
  • 39. this.target = config.target; var entity = this.target.entity, entityName = entity.getProperName(), bulk = entity.getInfoModeString(), html = '<h1>' + entityName + '</h1><div class=specification>{bulk}</div>', millisecondsStart, millisecondsTotal, millisecondsEnd, millisecondsLeft; if(entity.is('decoration')) { this.target.makeDynamic(); } this.infoPanel.innerHTML = ""; if(entity.contract) { millisecondsStart = entity.contractStartTime; millisecondsTotal = entity.contract.requiredTime; millisecondsEnd = (new Date()).getTime() - this.target.entity.contractStartTime; millisecondsLeft = millisecondsTotal-millisecondsEnd; if (entity.is('any house')) { if(this.target.entity.contractState === "started") { var gold = this.target.entity.getContractValue(); bulk = '<div class="progress"><div style="width: 0%;"></div></div><p>Gives you '+ gold + ' coins in <span id="timeLeft">'+utils.formatTime(millisecondsLeft)+'</span></ p>'; } else { bulk = bulk || '<p>Please connect to road</p>'; } } else if (entity.is('farm')) { var food = this.target.entity.getContractValue(); bulk = '<div class="progress"><div style="width: 0%;"></div></div><p>Gives you ' + food + InfoMode.prototype.activate = function (config) { ' food in <span id="timeLeft">'+utils.formatTime(millisecondsLeft)+'</span></p>'; this.target = config.target; } else if (entity.is('ship')) { if(this.target.entity.contractState === "started") { var entity = this.target.entity; bulk = '<div class="progress"><div style="width: 0%;"></div></div><p>Repair complete in <span id="timeLeft">' + if (entity.is('castle') && wooga.castle.playerData.upgradingCastle) { utils.formatTime(millisecondsLeft) + '</span></p>'; } wooga.castle.CastleCompleteScreen.instance().show(); } this.deactivate(); this.manager.setMode(wooga.castle.GameModesManager.Mode.BASIC); } else { return; if(entity.is('castle')) { } if(wooga.castle.playerData.upgradingCastle) { wooga.castle.CastleCompleteScreen.instance().show(); if (entity.is('decoration')) { this.deactivate(); this.target.makeDynamic(); this.manager.setMode(wooga.castle.GameModesManager.Mode.BASIC); } return; } this.infoPanel.innerHTML = '<h1>' + entity.getProperName() + '</h1><div bulk = '<p><span class="population">Supported Population: ' + class=specification>' + entity.getInfoModeString() + '</div>'; entity.definition.population + '</span></p>'; } else if(entity.is('decoration')) { // RADAR: [szafranek] Why is this HERE? Moving it to worldView and listening to "entity/focused" looks better to me. bulk = '<p>Nearby buildings get a '; var concatBoosts = false; this.manager.view.isFocusedEntityView = function (entityView) { if (entity.definition.goldBoost) { return entityView === config.target; bulk += 'gold boost of ' + entity.definition.goldBoost + '%'; }; concatBoosts = true; } if (entity.definition.foodBoost) { if (entity.contractState === wooga.castle.House.ContractState.STARTED) { if(concatBoosts) { this.showProgressBar(entity.contractStartTime, bulk += '</p><p>and a '; } entity.contract.requiredTime); bulk += 'food boost of ' + entity.definition.foodBoost + '%.'; } else if (entity.contractState === } wooga.castle.House.ContractState.CONSTRUCTION) { } this.showProgressBar(entity.constructionStartTime, } entity.contract.constructionTime); } this.infoPanel.innerHTML = html.replace('{bulk}', bulk); this.manager.view.isFocusedEntityView = function (entityView) { utils.publish('view:entity/focused', { return entityView === config.target; "entity": entity, }; "entityView": entity.entityView, if(this.target.entity.contractState === "started") { "infoMode": this var progress = document.querySelector('.progress div'), }); timer = document.getElementById('timeLeft'); progress.style.width = Math.floor((((new Date()).getTime() - millisecondsStart) / millisecondsTotal) * 100) + "%"; this.showBoosts(entity); counter = setInterval(function () { var now = new Date().getTime(), timeFromStart = now - millisecondsStart; this.show(); if((timeFromStart / millisecondsTotal) >= 1) { clearInterval(counter); }; } timer.innerHTML = utils.formatTime(millisecondsTotal - timeFromStart); progress.style.width = Math.floor((timeFromStart / millisecondsTotal) * 100) + "%"; }, 1000); } utils.publish('view:entity/focused', { "entity": entity, "entityView": entity.entityView, "infoMode": this }); //TODO move to pubsub // show boost info in influence area of decoration if (this.target instanceof wooga.castle.DecorationView) { this.target.entity.game.publish("decoration/showBoost", { entityView: this.target, x: this.target.entity.x, y: this.target.entity.y }); } // show boost of houses or farmfields in info mode if (this.target instanceof wooga.castle.FarmFieldView || this.target instanceof wooga.castle.HouseView) { if (this.target.entity.boost) { this.target.entity.entityView.showBoostInfo = true; } } this.show(); Original method after refactoring. };
  • 40. Know where to stop. Ask yourself: is the further investment wortwhile?
  • 43. coderetreat.org Organize one yourself if there’s none in your city!
  • 44. Robert C. Martin “Learning to write clean code is hard work. It requires more than just the knowledge of principles and patterns. You must sweat over it. You must practice it yourself, and watch yourself fail. You must watch others practice it and fail.” From the book “Clean Code”: http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882
  • 45. Write code for people Learn from others @szafranek
  • 46. _gee_ fingersss chez loulou diathesis linder6580 Pipistrula ginza_line Ocial GDC superfluity Łukasz Bałamut mooong photo credits