Friday, November 27, 2015

Project time estimates and Murphy's law


As we saw in the last few iterations, Murphy's law: "Anything that can go wrong, will go wrong" tend to be 
very strong resident in our midst.

Let's take a client-server short-project as an example. When you ask the client developer how much time will it take he answers 10 hours of uninterrupted work.
Usually a developer will interact with the world and his team mates (eat-lunch, do code-review, attend a meeting) , so let's count this as 2 days.


Now let's go to Murphy

client-code:
    1. Complexity in library-code:  You thought the default 1 row of code will do the work for you but it turns out it does not support your use-case, or t fails unexpectadly only in your input/
    2. Complexity in code: changing one element caused an unexpected bug in another unrelated component.
    3. Infrastructure failures:  your machine/network/server- can be down for few hours.
    4. computer/network/coruppted-int server can take 0-5 hours to overrcome
    5. Product requirements may slightly change when they see your finished feature. They don't ask for a new feature, they just explaining you what they ment before better.
UI related code:
    1. UI/Art integration may not be flawless, depending on your workflow
    2. Sometimes, you can't use the Art "as is" and it is not apparent until you actually try.
client-server potential issues:
    1. Miss-understanding of the API usage, can take some time to debug and understand.
    2. Sometimes it is not a miss-understanding, but a plain bug in the server-code.
    3. Sometimes it's not a bug,but a miss-configuration / not latest-version of code etc

So, there are at least 10 potential risks. What should be the estimate in this case?

One approach is to look at the optimistic scenario and say 2 days. It will sometimes work, but a lot of the time will fail poorly.

Second, opposite approach is to look at the worst case scenario and assume all the problems will happen simultaneously. This approach will say let's estimate 8 days.  One might say "better safe than sorry" and if it will be faster, the developer will say he is done.
A third approach is to aim to be correct most of the time (80-90%) but assuming that some problems might happen , but not all of them at the same week. 
Someone, on the upper-management has to fully understand this, and to manage the risk accordingly.
If you actually want numbers.  Let's say this is an example of (a-low) risk-matrix for the 2 days task. No real human think of this in this manner, but you do have "hunches" about it. In this case, I include a 0.1 chance fo r 2 extra-days due to bad-library behavior. 0.2 extra-day if sever-code does not work etc.

risk0.10.20.050.10.20.1
extra-day2.0015113

The probability of failure for this risks is:
46% - 0 late
71% - up to 1d late
83% - up to 2d late
90% - up to 3d late
94% - up to 4d late
97% - up to 5d late
98.7% up to 6d late
99.1% up to 7d late
99.8% up to 8d late (remember it's on a 2d task....)


Now let's talk about who takes the buffer.  
Let's say a developer gives the matrix to his manager, and ask the manager to tell him how to estimate the task.  What should the manager answer?
Due to Parkinsons's law "work expands so as to fill the time available for its completion", a reasonable manager should do three things.: 
  • Ask the developer to be 80% right, and in this case to add 2d to the estimate : 4d. Tell him that is is ok and fully expected to finish a day early, and they if all hell break loose, the manager got his back with few extra days.
  • Keep a mangment buffer for the next 17% (3 more days) which can be used by the developer, and will be totally ok to use, if the developer explained that the risk actually happened.
    One buffer pool can sometimes be shared amongst multiple team members.
  • Inform his managment about the chances.  If it's a live-or-die scenario, they may have their own buffer on the extra 3% risk.

























Sunday, March 8, 2015

Just few random notes on Parse and Mobile FileUpload


FileUpload on mobile

 <input type="file" accept="image/*">
Android (Chrome and 4.4 native) provides a reasonable, but not-pretty, interface.

If you want to use camera, add the capture camera option. Note that it will not allow other images.


Parse.com allows to upload the file and query it by URL. It`s very simple, but note that it means an image access counts as an API call, and as images usually come in packs, this can cost your quota.

Detect change on textarea

Use $('#textAreaId#).on('change',function() { do something here });
change will be called when the focus is changed and any of the inside-text change.

Parse model update

On many views, you will want to first refresh the view from a saved DB element, and then on edit update(re-save) the element and refresh the view again.
For this pattern, always keep a cached parse object which was returned from the DB and is connected to parse via the row-id.
On refresh, update the cached-element, if found in the DB.
On edit either re-save (on the cached one) if it already exists, or save a new one and create cache.

You can save multiple objects via Parse.Object.saveAll (array)



Parse queries
When querying a pointer, sadly, you can't just pass an instance of a previously returned item (item itself != item pointer). You have to create a pointer object. 
var pointerToUser = {  __type: "Pointer",  className: "_User",       objectId: myUserItem.id };

You can combine multiple queries using Query.or(q1, q2)

Parse weird save/query results

  • Parse will not fail if you query a db class which do not exist at all. Beware of Parse.Object.extend("SpellingMistakeWillNotBeNoticed");
  • It will also allow you to save db columns which do not exist at all. Beware of (item.set('spellingMistake',666).

Tuesday, February 3, 2015

Register/login



Jeff Atwood wrote a great post on creating the simplest login page. Read it!

Let`s use JQueryMobile for the widgests and Parse for the backed.
Insert this html to the JQueryMobile page:

<h3>Login/Register</h3>
<label for="usrnm" class="ui-hidden-accessible">Email:</label>
<input type="text" name="user" id="loginEmail" placeholder="Email">
<label for="pswd" class="ui-hidden-accessible">Password:</label>
<input type="password" data-inline="true" name="passw" id="loginPassword" placeholder="Password">
<input type="submit" data-inline="true" onclick="myForgotPassword();" value="reset pass"> 
<label id="loginMessage" style="color:red"></label>
<input type="submit" data-inline="true" onclick="myLogin();"       value="Log in">
<input type="submit" data-inline="true" onclick="myRegister();"    value="Create new account">


Use JQuery to extract user values and fill login message ($("#input_id".val() , or $(#message_id").text("new text")
We will use Parse user.signUp , Parse.User.logIn and Parse.User.requestUserPassword methods for the backend.



function myRegister() {
    console.log("myRegister");
    $('#loginMessage').text(""); //clean
    var email = $('#loginEmail').val();
    var password= $('#loginPassword').val();
    
    var user= new Parse.User();
    user.set("username",email);
    user.set("email" , email);
    user.set("password", password);    

    user.signUp(null, {
       success: function(user)  {
           console.log("register success");
           $('#loginMessage').text("success");
       },
       error: function(user,error) {
           console.log("register failed "+ error.code +" " +error.message );
           $('#loginMessage').text("failed "+error.message);
       }
       
    });
}

function myLogin() {
    console.log("myLogin");
    $('#loginMessage').text(""); //clean
    var email = $('#loginEmail').val();
    var password= $('#loginPassword').val();
    Parse.User.logIn(email, password, {
       success: function(user)  {
           $('#loginMessage').text("login success");
       },
       error: function(user,error) {
           $('#loginMessage').text("login failed "+error.message);
       }
    });
    
}

function myForgotPassword() {
    console.log("myLogin");
    var email = $('#loginEmail').val();
    
    Parse.User.requestPasswordReset(email , {
       success: function() {
           $('#loginMessage').text("password reset sent");
       },
       error: function(error) {
           $('#loginMessage').text("failed to reset password "+ error.message);
       }
    });
}

Tinder-clone in javascript

Tinder-like moving images:

Have a look at the end result here.
And now let`s go step by step:

[Step 1] http://jsfiddle.net/3y7fogdv/1/embedded/result/
  • Check if it actually doing anything: Use requeststAnimationFrame (+big function to make it better)
  • The actual moving of the element is done in js using: e.style.transform = 'translate3d('+newX+'px,'+ newY+'px,5px)';
  • To return it to first position, add style{transition:all 0.3s;}
  • To make sure the image is not dragged by itself:   user-drag: none;  -moz-user-select: none;  -webkit-user-drag: none;
[Step 2] http://jsfiddle.net/3y7fogdv/7/embedded/result/
Lets use a card-style (see here) and move the entire card.

[Step 3] http://jsfiddle.net/3y7fogdv/18/embedded/result/
Add tinder buttons  (V,X) below, add z-index to the .card ( z-index:1;    position: relative; ) so that it will move above them.
Also note that when we move the card down, it causes scrolling (solve by adding overflow:hidden to the main content) , and that the end of the content ends with the end of the buttons instead to extend to the full view. This is solved by a small js, to change main content size according to full size- header height.
Last change is addition of rotation to the transform  rotate( startX-newX)*0.05 + deg

[Step 4] http://jsfiddle.net/3y7fogdv/19/embedded/result/
check the release location on hammer "panend" event, and a check of ev.isFinal and ev.deltaX ev.deltaY.  If it is above a threshold, move it far away.
Also , let`s use multiple cards, so we will use multiple Hammer instances, one for each card. Luckily hammer supports it and hammer events are separated per card.


[Final notes]
On mobile, panning may cause scrolling the window down. To avoid this, add:
        $(document).bind('touchmove', function(e) {
            e.preventDefault();
        });