Cheap is Expensive

Cheap is Expensive

The common law of business balance prohibits paying a little and getting a lot – it can’t be done. If you deal with the lowest bidder, it is well to add something for the risk you run, and if you do that you will have enough to pay for something better.

Attributed to John Ruskin

The cheaper the software development is, the more expensive it will be. When people seek to develop a product, it can be hard to understand all the costs that go into it. Software development can be like an iceberg in which 90% of the structure isn’t visible to the end user.

Beyond wanting to retain as much money as possible, cheap development can be appealing because it can seem like:

  • Development can occur anywhere
  • The software functions are simple
  • When the software is completed, other, more visible “higher value” items can progress
  • Software development isn’t that hard
  • Development really can occur anywhere. The problem is that accountability can be hard to ensure from a distance. When you don’t have the ability to directly supervise the hiring process, training, and code quality you have no way of knowing what you’ll really get. There are things you can do to mitigate the uncertainty that occurs with remote or offshore work, but they cost money and time and require expertise in software development. If you don’t have someone on your team with that expertise, remote/offshore development is a huge risk. Supervising a remote team is not the same as supervising a team on-site.

    Simple does not mean that a function is easy or fast to make. A really thorough exploration of the implication of the product can show the unintended consequences and supporting structures to operationalize the features. If there is any data that is supposed to be stored and retrieved, then your product will need some sort of storage and database.

    If your “high value” items are built on a fragile piece of software, then that value will quickly be lost as it breaks and loses customers or never functioned in the first place. You can’t rob Peter to pay Paul. That never turns out well.

    It is entirely possible for a developer to put a pretty face on a project to cover a mess of chunks of broken code underneath. Worse than that is a product that functions at the moment of release, but would require a complete rewrite to update because the developer didn’t architect the project well. Yes, many people can write some code. That is not the same thing as architecting a new product.

    Software development, like a lot of skills, is progressive. When you hire an inexperienced developer, you are paying for their education as much as you are their code. That balances out on a sufficiently large team and is part of the normal arc of growing new members of any profession. If you are going the cheap route, you’re probably going to be working with more inexperienced developers and that will be reflected in the ultimate cost.

    We’ve seen projects in which the initial development was an oozing pustule of code with functions distributed widely throughout because no one with sufficient expertise thought through the architecture and the implications for each of the functions/features upon it. This code quickly drags down the whole project when later developers attempt to update or add a feature and find they can’t because the scattershot code can’t be modularly replaced or improved. You can’t remove just the tumor, because the cancer has spread.

    Even worse than that is when a business falls into the sunk-cost fallacy and just can’t let go of some really nasty and fragile code. Rather than scrap it and start over, they spend an insane amount of money frankensteining more code on top of it to keep it limping along.

    The time you spend untangling a poorly developed product and repairing it costs more than the expense of the developers to test and correct the code, it costs customers. You lose revenue the product would have earned in the delay coming to market.

    There is a mistaken impression that producing software is so easy that high school and college students working alone are creating billion dollar companies. It is true there are prodigies; rare individuals whose monomaniacal devotion to a single product is the foundation for something larger. Though even they had early supporters and funders to enable their seed of an idea to scale.

    Even though finding a bargain on development is a bad idea, there are still many ways to prevent your project from bleeding money.

    Be wise about your development expenditures:

  • Map out the functions of your product to prevent wasteful redevelopment and inadequate architecture.
  • Learn about the process of software development to become an informed consumer so you understand what the developers are doing if not how they are doing it.
  • Have a software development-specific contract; buying software development is not the same as other services.
  • Get clear Statements of Work that show exactly what will be developed and what that code will do.
  • Have highly specific Acceptance Criteria and a plan for remediation if the software doesn’t meet that criteria.
  • Being wise about your expenditures will save you the costs associated with being unprepared and uninformed. Be frugal, but not cheap.

    Cheap is expensive, expensive is cheap.

    5 Important Mobile UX Areas People Usually Forget

    5 Important Mobile UX Areas People Usually Forget

    At Salty Dog we help clients to define their app. Many clients know how they want their app to solve a particular problem but haven’t figured out how the app will look from the perspective of their customers.  There are some key UX areas they often forget that can have a huge affect on the user experience.

    Here are some of the most easily overlooked UX areas:

    Login and Registration

    Login and registration are usually the first part of an app your user will see. Some commonly forgotten sub-use cases are: forgotten password, registering email, forgotten password but haven’t verified their email, or re-sending email verification.

    Forgetting passwords can happen more frequently on mobile devices. It can get really annoying for a user to try a bunch of passwords, then have to go back to the main website to request a new one.  This is amplified if the registration requires email verification, and they are denied a new password because they haven’t completed this process.

    The solution to these issues is to remember some of the more subtler use-cases. Here are the  use-cases for a complete login and registration experience.

  • As a user, I would like to register/create a new password-protected account, in case I don’t have one yet.
  • As a user, I would like to authenticate with the app, so that my information is protected.
  • As a user, when I start the app I would like to know if I haven’t validated my email address if I haven’t, so I know to look in my inbox or spam folder.
  • As a user, I would like to be able to resend the email verification in case the old one is expired or lost.
  • As a user, I would like to be able to set a new password via my validated email address in case I’ve forgotten my old one.
  • As a user, I would like to be able to log out of the device, in case I want to restrict access or log in as a different user.
  • As a server administrator or app owner, I want a way to log a user out, in case there is a good reason I want them to re-authenticate.
  • Application Updates

    Sometimes it is necessary to make changes to the way the app works and require users to update. For example, there might be a change to the way the server works that breaks any previous versions of the apps that haven’t updated, or there is a really important security fix.

    The solution for this is to provide a way to make sure the user updates the application, and then gracefully degrade functionality if they do not update. A simple flow outline might work like this:

    1.On app start, connect to the server supplying the version of the application.
    a. If the version does not need to be updated, then respond with an OK.
    b. If the app DOES need to be updated, then respond with a “needs update”.
    2.If the app received an “needs update” response from the server, then:
    a. Inform the user they need to update.
    b. Let the know that functionality of the app will be impaired if they do not.
    c. Give them an easy way to facilitate the update such as a button that takes them to the App Store or Market app to update.

    Important Notices

    It is really important to be able to send notifications/notices to the user. The purpose can be anything from letting them know about planned server maintenance, other companion applications, updates/upgrades to the functionality,  or bug fixes.

    What makes this so critical is because of how difficult it can be to communicate with the user. You need to communicate with them so you can set their expectations about app functions and changes. In the absence of information, the user may jump to negative conclusions that can result in bad reviews, or worse, uninstalling your app.

    The fix is relatively easy and accessible: add a way to send notifications to the user. There are many third party services that can do this. Some even allow targeted notifications to segments of your users.

    Alternatively, you can implement your own solution. The messages can be displayed using webview controls, allowing you to point the user to blog posts or release notes. The app just needs to be smart enough to know if the user has seen the notification before so they don’t get spammed with notifications.

    Network Problems

    Issues related to the network or connectivity are ignored a lot of times, and are only discovered in testing or when a user has issues with it. These fall in three basic categories:

    1.Server issues. The server might have an issue. The cloud provider may decide to reboot your server. Even though the network is functioning correctly, the server may not respond.

    2.Network issues. Sometimes there are outages either at the ISP/provider level or somewhere in the backbone.

    3.Device connectivity. It could simply be an issue with network connectivity from the device. The user is out of cell range, or service quality is low.

    4.Planned outages. The server might need to be upgraded, or the system restarted.

    There are two basic ways of dealing with this. First, design the app with lack of connectivity in mind. For example, if the app cannot reach the server then handle it gracefully. When the network is not reachable, you can give an indication in the app so the user has an idea of what is happening. I’ve seen some apps show an indicator in the title-bar.

    Second, leverage the Important Notices suggestion above. If you’re using a 3rd party notification service, then you can inform users that you’re aware of the outage and when they can expect it to be available again.

    Interacting With Someone Who Doesn’t Have the App (Yet)

    When designing social apps such as chat, it is easy to forget about the case when one person has the app and they wish to interact with someone else who doesn’t. This is important because it can provide a viral way of gaining more users.

    We’ve blogged about this in detail here and here.  The basic problem is how to interact with someone who doesn’t have the app yet. Once you solve the interaction problem, how you can turn that into a seamless onboarding process for a new user.

    The answer is to use something called deep-linking in conjunction with SMS messages and a bit of magic from the iOS web-view control and Android App Store broadcast message. This allows you to send an invite link to someone over SMS, which sends them to a landing page that includes links to the appropriate web-store to install the app. After they’ve installed the app cookies can be used on iOS or broadcast messages from the Google Play Store App, to let the newly installed app know there was an invite from someone else.

    This allows an invite to go to someone without the app, them to install the app, and then pick the invite up from where they left off as if they had the app installed in the first place.


    When making your mobile project it is important to remember what happens after the initial release and use. As the overall quality of apps has improved, sensitivity to clunky apps has increased. Failing to include these user stories is very likely to result in a user deleting your app.  These user stories should become a part of your larger release strategy to help you plan for ongoing updates, user engagement, and to ensure that you have the infrastructure in place to support users when they have a problem with the app.

    Using Azure Functions and Document DB for Simple User Authentication

    Using Azure Functions and Document DB for Simple User Authentication

    Often my clients need simple, inexpensive, yet scalable solution for user management. This is especially true of clients that are startups, or are developing MVPs. Cost and scalability are important.

    Facebook’s server was my go-to tool until Facebook kicked it to the curb. It has since been open sourced, but still remains somewhat of a black box. (Unless you want to learn a new framework.) Now that Microsoft has added Azure Functions to its lineup, there is something simplier to set up.

    With Azure, it is easy to implement a simple authentication system that uses best practices for passwords and hashing, and the creation of JWTs.   Azure Functions are Microsoft’s answer to AWS Lambda. Like AWS lambda, Azure Functions are serverless pieces of functionality triggered by a customizable variety of things. You are charged by the amount of memory in GB/sec for the time consumed by each individual action.

    “HTTP triggers” are a REST endpoint that executes a specific Azure Function. There are other kinds of triggers such as queue events and timers. This post will focus on HTTP triggers.

    Azure Functions are built on top of Azure Web API, but each function is in a separate directory. Azure function supports multiple languages, such as C#, python, PHP, JavaScript and others. I opted to use C #, as that is my language of choice.

    An Azure Function

    There are a couple configuration files in each directory which let Azure Functions know several things related to each function. In the case of HTTP triggers, this includes the REST route and security on the function.

    Files in an Azure Function directory.


    Though Azure Functions supports debugging, they don’t yet support unit testing. To make it easier to test this project, I put the bulk of the functionality into a shared assembly. The Azure function calls into the shared assembly which parses parameters, delegates to sub-components and formats the output. This allows me to quickly develop and debug the basic functionality. Additionally, it should be compatible with Azure Web Api projects, though I haven’t tested it yet.

    Simple Authentication

    This simple authentication has 3 HTTP methods: Register, Login and GetCurrentUser. The first two methods are straightforward. GetCurrentUser is there to show how to validate the java script web token (JWT).

    For data storage I decided to use Microsoft’s NOSQL solution DocumentDB. DocumentDB’ s pricing model is based on reserved units of processing and max storage size. (Check for more pricing details.)

    This project uses a simple abstraction for database access to facilitate mocking for unit tests, and to allow a way to use another database. The functions are very thin wrappers around calls into the shared assembly which coerce values, validate input, catch errors and return results accordingly.

    There are three layers. Layer one coerces parameters from HttpRequestMessage objects and formats the HttpResponseMessage. It calls into an object that does the bulk of the work but has no awareness of http request or response messages. Finally, there is a database abstraction layer, which is concerned with finding User objects based on ID, email address or email validation code, and saving the user objects.

    Creating a User

    I followed best practices by using hashed passwords and user specific salt values. Additionally, I decided to use JSON Web tokens that can be passed into API calls for when the user is authenticated.

    User registration simply becomes a matter of making sure the normalized email address is unused/unique, initializing the salt value, and then hashing the password storing it. I used code from here (reference) to store the hashed password. The hashed password and the salt are stripped from the user data when returned from a function. At this point I do not issue a token, because in the future I will require email validation.

    Authenticating a User

    User authentication takes an email address and a password. The user record that corresponds to the email address is retrieved, and the attempted password is hashed using the salt from the record. If the hash value and the hashed password in the user record match, then I return an HTTP 200, a sanitized user record, and the JWT. The JWT is non-standard right now and simply contains a creation date and expiration date, and the ID of the authenticated user.

    If the two hashed values do not match, and HTTP code of 401 (unauthorized). It is considered best practice to give no indication if it is the email or the pastor that is incorrect so that they would be hacker cannot test for valid emails.

    Verifying the User Authentication

    If there are other Azure Functions that require the authenticated users, the JWT needs to be passed in. To check it, it needs to be unwrapped, the signature tested, and the expiration time checked against the current time. If the signature is incorrect, or the expiration time is passed then an HTTP 401 would be returned.

    Of note: JWT is what is as known a bearer token. This means that no matter where this token comes from it is considered good. Some people add the hashed IP address that was used to authenticate to the token, and that is compared at the time the particular call is made. If the hashed IP address don’t match, then it is assumed that the token is invalid:

    A couple notes about the code.

    First, I use the JWT library from here However, the nuget package is compiled for .Net 4.6, but Azure Functions only work with .Net 4.5 as of this writing. For ease, I just copied the source in verbatim.

    Second, I created some extension methods around the User object to create objects, set passwords and test password matches. Likewise, there is a helper class for JWTs called Tokens.


    To make it somewhat easier to test I wrote the Azure Functions to work as either a GET or POST. That way I can simply use a browser and type in the correct values in that URL and press enter and observe the results.

    Here is the response indented.

    Next Steps

    There are several things I will add over time:

    • Controlling what gets returned from the GetCurrentUser call.
    • Common UX patterns such as the onboarding process (see User Experience:  Inviting users to your Android or iOS app Part 1 and Part 2)
    • OAuth 2.0 and/or OpenId connect. Since this supports JWTs, it shouldn’t be too hard to add the extra steps that allow for OpenId connect.
    • Let’s encrypt support. There is a Microsoft Web API plugin that can do this. and since Azure Functions use Web APIs, it isn’t too hard to get it hooked up.


    Testing: Skipping this is VERY Bad

    Testing: Skipping this is VERY Bad

    Calculate what you hope/expect your product to make. Now imagine 90% of that profit being cut out because the first 10% of customers rated your app poorly. Now you have the value of Quality Assurance (QA) and Usability testing. You really can’t afford not to do testing.

    Despite the high stakes, testing can be an undervalued activity in software development. There is a temptation to leave it until the very end, or worse, to let the customers do it. Assuming that the software engineers will make sure their code functions as they write it and that will be sufficient is a great way to connect with disaster. Many teams don’t even consider usability testing at all.

    One of the cheapest and most powerful things you can do for your product is to test usability before you begin programming. Create mock ups of the screens and print them out and show them to a focus group of potential users. What “buttons” would they push? What do they think will happen if they take a specific action? How is that different than what you had intended? Starting with usability will likely create changes to the design of the project. It is FAR less expensive to do that before the code has been written than after.

    One of the delightful things about writing code is that it can be like putting together a puzzle in which connecting one piece to another can cause the picture to mutate. There is an old programming joke based on the song, “99 Bottles of Beer on the Wall.” It goes, “99 bugs in the code, 99 bugs in the code, take one down, patch it around, 117 bugs in the code.”

    If you wait until the end to test, you might run out of time or money. Waiting until the end risks finding a catastrophic bug that has gotten very expensive to fix. Testing should occur concurrently with coding.

    Testing is a specialized skill. To thoroughly test a product, a QA tester will create a plan that systematically exercises each of the different parts of the code and the whole. They document the outcomes of those tests in a clear and technical manner, so that bugs can be reproduced by the programmers.

    The focus of testing is different than that of development. Development is trying to solve problems, QA is trying to find problems, and these are different mindsets. In some ways, QA testing is a bit like proofreading, and is most effective when someone different than the original programmer performs the tests. When a programmer tests their own code, it is very easy for people to miss or overlook errors because they are so familiar with the functions, they don’t even realize when they aren’t working properly.

    Testing saves development money and time over the life of the project. It may seem like it “costs” more in the short run, and it can be hard to measure the value of the problems you don’t have. But, to get a picture of the value of testing, go to the Google Play Store or the iTunes App Store and look at apps that have poor ratings and read the comments. What are people complaining about? Things not functioning right or as expected. Too many complaints about functioning, and your app will be removed from the stores.

    Iterations Keep You in the Loop

    Iterations Keep You in the Loop

    There are some developers who will do all the development prior to revealing the product to their client. This is a great way to get something other than what you had wanted and have little to no recourse to get it changed or avoid cost overruns.

    With iterations, you are never surprised by the product delivered to you.

    At Salty Dog Technology we use an iterative process.  The iterative process ensures that the client is getting the product they want and need.  

    An iteration is a block of work, usually encompassing a single function/feature or group of related functions/features.

    An example of a single iteration:

    • Describe the work to be done in detail in a Statement of Work. 
    • Get approval from the client.
    • Wireframe work to be done.
    • Get approval from the client.
    • Code the functions/features.
    • Demonstrate functioning of functions/features to the client
    • Deliver code written to client

    An iteration usually takes anywhere from 1 week to 1 month, depending on the turnaround time for questions and the amount of work to be done.  Iterations help you to minimize risk, as you are constantly getting to see/use/take possession of work done throughout the process.   

    Another advantage of iterations is that course corrections can be made early. … explain… … Work occurs on your schedule.

    For example: Imagine you want an app. You have imagined that your app has three core functions. With the iterative process each function may be an individual iteration.  After coding the first function, we realize that there are multiple possibilities for the way that users are directed to the second and third functions.  You get to use the app and we discuss which of those possibilities make the most sense for your app.  You chose one, and we code the next iteration.  The finished product contains the features the way you wanted them to appear based on your in-depth knowledge of the market for the product/use of the product.  

    Now imagine that you approached a developer who agreed to make the app for you but who doesn’t work iteratively or in partnership with you.  You tell them what you want the app to do.  They begin coding and when they face a decision point- they make it- you are never consulted.  You receive a completed app.  It might be what you wanted.  It might not.  You don’t know what you’ll get until the very end because you were forced to take a blind leap of faith that they would finish it appropriately.  

    We don’t ask you to walk off a cliff and hope there is a net below to catch you.

    We want you to feel confident that the product being built is what you want and need.  We want you to be the one to direct the project and make choices.  We want to set you and your product up for success.   

    So- when you hear us talking about iterations- this is what we mean.  It is one of the many ways that we keep you in charge and in the loop.