What is computational complexity ?

Fractal - computational complexity

Complexity analysis leans on the more theoretical side of computer science. Ironically, I know, given this site’s motto, however, bear with me, it might just be worth it at the end.

Allow me to answer the first question you should have: What exactly is computational complexity ? I’m glad you asked! Computational complexity is one of the measuring sticks we’re using to compare different solutions, in an attempt to decide which one is the better choice.

What are we measuring ?

The goal for us is to decide which solution is better. That means, usually, how fast does the algorithm do its job. The problem with this approach, is that computational speed is influenced by both algorithm design and the hardware it’s run on. It wouldn’t be any fun to always decide that the best algorithm is the one that runs on the fastest computer, right ?! The good news is that a better algorithm will always run faster if the input is large enough. I classic fables terms: any slow turtle can beat the hare if they run on different tracks (In this analogy, the animal is the computer and the race track is the solution).

This sounds hard – How do we measure this?

Counting all the operations the algorithm makes is borderline impossible so instead we try to get a feel of how many operations we need to do for a given input. So you have to settle for an approximation, the upper bound of that approximation is the famous big O notation.

To get a simple form for of the big O notation you can do this: If you have of n elements as an input and your program does let’s say 45 * n + 55 operations, to get the big O value, you ignore the constants and keep only the biggest term involving n. So the big O notation for our algorithm would be O(n). This means that if you double the input, your program will run for about twice as long.

More so: When you add two solutions with big O notations (as in solve two problems one after the other in your program) the bigger one wins. That is O(n^2) + O(n) = O(n^2). If the alternatives are equal, you can pick either one. This is a quick and dirty way of getting to a solution, which tends to work in practice when dealing with algorithms. There are times when the bigger term is not obvious. You then have to go back to the basics and find a formula that serves as the upper bound for both therms.

Multiplication happens when you solve a different problem on every step of your algorithm. It works pretty much as expected, as in O(n) * O(n^2) = O(n^3).

If you want to read more about the subject, you can check out the Wikipedia article for big O notation

Time for some examples

Let’s take some examples and see what’s their computational complexity.

O(1)

The execution time remains the same, regardless of the input size.

  • Basic arithmetic operation (addition, subtraction etc)
  • Accessing elements in an array by index.
  • Searching for elements in a hash-map / dictionary object
  • Searching an object in a db using an indexed column
O(log(n))

If you can’t design anything that works in O(1), this is  usually the next best thing. You can double the input for the run time to increase by one unit. Pretty sweet deal I’d say. Some common algorithms are:

  • Searching an element in a sorted list
  • Insert / delete operations on a binary tree / heap
  • Raise to the power (Exponential by squaring)
    • Theoretically this is not O(log(n)) because the result size can grow linearly with the input size. However, I still think it’s a neat algorithm and worth mentioning. Furthermore, it behaves quite well in practice.
O(n)

The execution time grows with the input size. Doubling the input size doubles the execution time. Not a great deal, but it could work.

  • Search an element in an unordered list
  • Searching through a  table using an indexed column.
O(n*log(n))

These are slightly more inefficient than O(n), although they offer pretty good performance.

  • Sorting – mostly sorting algorithms.
O(n^2)

The execution time grows squarely with the input size. Therefore, with double the input, the program takes four times as long to finish. 

I’m adding to this bucket all the polynomial algorithms (O(n^x), regardless x). They are pretty similar so it’s not exactly cheating. Just go with it!

  • Generating pairs of elements
  • Inefficient sorting (bubble sort)
  • Joining tables
O(n!)

These are exploration algorithms. You get the input and you look at all possible solutions to see which ones work.

I’m also including here exponential time like O(2^n). The execution time grows so much that on an average computer you can usually handle an input size in the low tens (i.e. 30 / 40). Avoid using these if you can.

  • Backtracking
  • Traveling salesman problem

Disclaimer and other notations

The big O notation is a simple way to provide an upper bound on the growth rate of the execution time. In most cases that is enough, however, there are other notations that can be used if the situation demands it.

  • Ω – big omega  – the execution time grows at least as fast as the notation. It’s a lower bound on the execution time.
  • Θ – big theta – the algorithm is bounded by both big O and big Omega. This can translate into: the execution time grows as fast as the notation.

We also have a little variant for all the notations, I’m not going to get into any details, but I just wanted to let you know.

 

 

How to step up your automated tests game

fixing unit tests

This is a follow-up of my previous post here. If you’ve ever had to write automated tests, you might have some bad memories lingering around. Automated tests have a way of becoming troublesome as time goes by. You end up spending more time fixing the tests than adding actual features. When that happens, you don’t care about test anymore, you don’t run them, you’re not worried when they fail and you just don’t add new ones. That leads to brittle code and things break really bad really fast. I’m here to tell you there is a way to keep automated tests manageable.

Simple is better

Simple tests are the better tests. They should be simple to read, simple to run and simple to add. Clean up the setup. Wrap complicated setup in nicely named functions. Keep the logic linear. You should never have an if in your tests and you should scarcely use for loops or other exotic structures and/or logic. It’s as simple as this. When you write simple tests, you get easy to maintain tests.

Be precise

It should be clear what you are testing. A very good tip I have for you on this is to use the given/when/then format. What is that, you ask?! It’s a way of explicitly splitting your tests into three distinct sections.

Given should deal with setting up the data. Everything your test needs to run smoothly. This includes, but is not limited to: seeding the database, adding any mocks required, pre-generating any data and constructing all the models that you will use later.

When is where the magic happens. You test whatever you need to test here. You should make sure it’s obvious what you are testing. If you feel you’re not being clear enough, then wrap the call in function and name it accordingly.

Then is where you check your data. You have to make sure your test did what you want after all. The checks should be clear and self-explanatory. Additionally, the checks should be the direct effects of the when section. Don’t do unrelated assertions, even if they are true, unless you like to fix unrelated tests for every change.

I prefer using comments prepending each of the sections to make the tests even more explicit.

Test one thing at a time

Try to keep your test scope as narrow as possible. There is a two-fold benefit to this approach. First you get a clean list of failing tests when you break something. Hopefully you have named them well enough to diagnose the issue with just that. Secondly, updating the tests to match the code after you make a change should be a lot easier.

As I have hinted, naming is important here. The test name should hint at the non-trivial setup required and contain the assertion being made. A big red flag is if you find yourself having to use and in the test name. Some examples of some good test names are:

if the user is unauthenticated the server should return unauthorized
if the user is unauthenticated the server should log the request

A bad test name would be:

if the user is unauthenticated the server should return unauthorized and log the request

You’re actually trying to test two behaviors here.

Can you think of any other useful advice that could help us all with our tests.

Greenfield project – how to get it right from the get-go

You lucky programmer you. You get to work on a new project. A shiny new toy for you. All the possibilities that lay ahead. Simply limitless! Now the only question is: how do we do this right? How do we make sure our little project does not end up a big ol’ mess that is impossible to handle.

Decide on the spec

Before you can get your hands dirty, you have to have a plan. Right? You should have a very, very good idea what you have to do. Here are a couple of questions that you should have the answer to before you start any work:

  1. What are you trying to solve with the project ?
  2. What is the workflow of the project ?
  3. What is the expected load (now and in the future)
  4. What is the most important feature ? If you have to settle for three features, what would they be ?
  5. Are there any risks you need to look out for ?
Start reading

Now that you know what you’re doing, start reading! Honestly, there is a lot of reading involved before starting a new project. You should know what the available frameworks are, what are their limitations, what are they good/bad at. Additionally, you should have a good idea what add-ons you should use for what tasks and how well documented they are. Another important thing you should look into if known issues. There is a lot of less-than-stellar code out there, you should make sure you’re not relying on one for your awesome project. So, start reading now and don’t stop until you know everything. Well, maybe not everything, but you have a good idea what adding a particular framework means. Little word of advice here, if I may, just because you have experience with something, it does not make it the right tool for the job. Don’t skip over this stage.

Decide on the tech stack

Decisions, decisions! There are a lot of choices out there. Some are more mature (like Spring), while others are quickly changing offering new shiny toys at every turn (like React). You need to make a decision now that will affect the project it’s whole life. No pressure, right ? This is also the stage where you decide the infrastructure: what database will you use ? Will it be hosted on your server, or somewhere in the cloud ? What OS will the server be running on ? How will you do the deployment ? All of these are important questions, so take your time thinking about them now. You’ll thank yourself later. This is also the stage where you are in a safe position to make promises about timelines for your project, set deadlines, milestones, you know, all those things.

Set some ground rules

The beginning an important time for your project! The rules and conventions you set here are going to stick around for a long, long time. Be careful what patterns you add to your project here, they can save you or hurt you easily later down the line. Read about your tech stack’s best practices and make them part of your project. It’s going to be a lot of chaos in the early days of the project. You should be vigilant, so that you spot the changes that go against your rules and correct them.

Good job bearing with me this far. Now it’s time to go out there and make your dream project happen! Cheers!

8 git commands I use everyday

If you are already using git, then you can skip this paragraph. If you are fist hearing this word, then I can give you the basics for now. Git is the most used versioning control system. It basically is a way you can track all the versions of the program without having to get creative with folder names (version1, version1final, version1finalfinal etc.). Git also provides a way for multiple people to collaborate on the same project without stepping on each other’s toes (most of the time). You can get started with this tutorial. If you don’t know git you should start learning it now. I can’t stress enough how important and widely used it is.

If you already know about git, then you should have realized by now that keeping the git history clean is really helpful. It makes it easier to see what is merged and what needs work. You see when the changes were made and you can search through them for something that you want. It worth the effort to keep the history nice and organized for everyone. Without further ado, there are 8 git commands I use every day

git log

This is a nice and easy one. It shows all the history of the project, commit hashes, commit times and commit messages. You can search through the history by typing “/” if you want to look for something in particular.

git checkout -b branch_name

This creates a new branch of my current branch and checks the new branch out. Nice and easy way to get the task going.

git diff --cached

Once you add the changes you want for your task, you can review the staged changes to make sure everything is as expected. This command, together with git status, should save you from any surprised when doing a commit.

git commit -m "first message" -m "Second message"

The first message is a short description of what the changes are in this commit. I provide more details on the changes on the second message, which usually spans across multiple lines.

git rebase -i hash

Generally, when I work on a task I end up with a lot of commits on my branch. You know the ones: attempt at something, first iteration on something else, some minor change, oh now there is a typo. I like to have my tickets in a single commit when merging. This way, in the unlikely event, that I need to revert a ticket I can do that with just one commit.

git rebase master

I rebase a lot. Anytime I want to merge something I do a rebase on master first. This way you neatly stack all the commits for a ticket together. Makes following the git history much easier and reverting a change is easy peasy lemon squeezy.

git merge branch --no-ff

I very much prefer merging without fast forward. You get a nice commit message to celebrate and an easy to find point in the past to checkout if the need arises.

git push --delete origin/branch

Now it’s time to remove your work in progress and start anew. Great job! I prefer the more verbose way of deleting a branch. It’s harder to mess it up.

Let me know what other git commands you use everyday ?

Async programming models part II

As promised, this is the second part of my pocket-book introduction to async programming. Let’s continue!

If this article does not make sense, please check part I before shouting at me!

Map-reduce models

The map reduce approach is widely used in doing big data analysis. The model itself it’s a variation on the split-apply-combine approach, which is pretty much self-explanatory. You split the data, you send to multiple threads to tinker with it, you combine the resulting data and you get the result.

What are they good for

Map-reduce models are good at doing the same operations on large sets of data. You need to analyze numerous files, do it with a map-reduce model. Any kind of data which you can safely split into chunks, you can process with map-reduce.

Problems with map-reduce

With this model, you don’t have that much control over the threads. You just tell the thread what to do, and, generally speaking, it’s the framework’s job to split the data. Additionally, all of your threads are applying the same operation to the data set.

Map-reduce is not that good at handling interactions from the outside. If you need to handle user input, maybe this is not the way to go.

Map reduce usually implements the fork-join model. I.e. before the data is processed, you need to wait for all the threads need to finish, so if you have long tasks, then you’re going to keep lots of resources busy. Moreover, if you have network calls inside the map thread and one (or more) of those requests hang, you’re going to be in some trouble.

Where can I find this

Java parallel streams

Scala parallel collections

C++ OpemMP (albeit OpenMP offers more options than just map-reduce)

Promises

Oh boy! Promises are my new favorite toys for a while now. With this model you need to define a chain of actions that need to be performed on the data. Each action generates data that will be used by the next action and so on. After you do that, each action will be completed eventually (presumably on a thread that is not doing anything important), then the data is going to be passed to the next action and the cycle repeats.

What are they good for

Promises are a good way to parallelize a wide variety of independent flows. You have precise control over the actions in the flow and the order in which they are executed (inside the same flow at least). They can be returned from functions and that helps you keep your code cleaner and organized. Even more, you can bundle them together into bigger and more complex promises. You can pass any data to each of the actions in the flow. However, at the same time, you can do type checking on the data.

To me, the promises model is the most flexible model out there.

Problems with promises

They are hard to keep. Well, maybe not, it depends! However, the promises model is a bit hard to get the hang of.

Even though actions in a flow are executed din sequence, you have no control on the order of separate flows. This can be a problem.

Before you can use the data from the promise in the main thread you need to wait for the promises to complete. However, if you do lots of waits, (and in the wrong order) you may in fact hurt the overall performance.

Where can I find this

Javascript promise

Scala futures

Java futures

 

Async programming models part I

Asynchronous programming is everywhere around us and it’s here to stay. You might have noticed that CPU’s keep getting more cores as time goes by. Therefore, it would be a shame to let all those cores go unused in your app. In this article I shall be your guide through the various async programming patterns that I have used.

Plain old threads

These are the most powerful tools at your disposal. A kind of all-purpose swiss army knife that you can do everything with. The basic pattern is that you have a function that is going to run on a separate thread. You pass in any data that the thread needs and you are good to go.  Every pattern we will cover here can be reduced to this model (because this is what computers understand).

What are they good for

Plain threads are good for long, custom tasks that you need to run in the background. Do you want to build a server that handles multiple requests at the same time, then this is the way to go.

The problem with plain old threads

Albeit they are very powerful tools, threads can be a bit hard to use. You need to make sure you are passing the right data. Then you need to make sure you are getting the right data from the thread. And you need to all of this while make sure you are not causing any synchronization issues. That is not as easy as it seems. All the patterns share the synchronization issues.

Additionally, you need to decide on the number of threads to use and this is not a trivial task.

Where can I find this

All programming languages worth a damn provide some sort of API you can use for this

Queue of events

In this async programming model you begin with a bunch of threads that idly wait for something to happen. You also have a queue of events that keep track of what happens. When something happens, one of the threads picks up the work(usually the first idle thread). Then it does the work. Then it goes back to twiddling its thumbs until something new happens.

What are they good for

You should use this pattern when you need to respond to something happening in the world. These events can be HTTP requests if you’re building a server, user actions or other threads work.

Threads can be added or removed from the pool as needs demand. Most of the times this can be done automatically, so this is one less thing you need to worry about.

In simpler terms: If your app can be described as When this happens, then this should happen!, you can use this pattern.

Problems with a queue of events

You have significantly less control on the threads than you would normally do. Additionally, you need to add all the info required by the thread in the event data. From time to time, this can be tricky.

The queue itself can be a problem. Events can keep piling up during busy times or if there is a deadlock with the threads. If the queue grows too big events can either be dropped (causing loss of data) or they can block the system (causing much worse damage)

Where can I find this

This is all for today. Map-reduce and promises coming soon. Are there any async patterns you want me to cover?

Be a more productive coder – Some metrics you should use

What does make a good coder ? Is it the number of lines of code they write in a day ? How about the number of works they put in the office? Maybe it’s how fast they code, how quickly they solve any given issue? Well, as you might have guessed by now, I don’t fully agree with any of these statements. Here are some metrics that better reflect your proficiency as a  code writer.

Is your code easy to understand

You should always write readable code. I have an article here if you need a more in-depth opinion on what good code means. Being a productive programmer it’s not just about you. Your part of a team, a team of programmers who need to spend time understanding, maintaining and changing your code. If you have to spend a few more of your minutes to spare a few more minutes of some of your teammate’s time, then you have made the whole team more productive. Neat, isn’t it?

Forget the clever hacks and neat tricks you are tempted to use. I know, it’s hard to give up on a clever little idea you have. It happens to all of us, all the time. When it makes the code hard to read, it’s probably not worth it.

Is your code correct

This should be pretty obvious: Don’t write bugs. What it’s not obvious, however, it’s how costly bugs actually are. Tiny, little bugs cause big overheads. The QA need to file a bug report, it needs to be prioritized, scheduled into the current development cycle (call it sprint, or whatever you are using). Someone needs to pause their assigned work and start fixing it. This means they need to get up to speed with the area of the code the bug lives in, write the fix, review, and the QA needs to test it again. When you think about it this way, adding a few more automated tests and spending a couple of minutes manually testing your code doesn’t seem that bad. Am I right, or am I right?

Is your code flexible

To know if you are doing this, just remember what were your feelings the last time you were asked to change something you wrote. If your first though was: Noooo! Please don’t make me do that!!! then you should pay more attention to this. Keep in mind that requirements change all the time, and that the code is something that is constantly evolving. Therefore, you should write the code on the assumption that you will need to change it at some point, because it’s a safe bet you will. If you spend your time wisely now, you will save a lot more time later. Flexible code is an investment in your future productivity.

An important disclaimer here. Don’t overdo this. It’s a fine line between keeping your code flexible and spending waaay too much time preparing for a change that may never happen. If you are unsure, just ask someone. The PM/tech-lead/client should know how likely a change is, hence they should be able to give you the answer if it’s worth doing it. Design patterns are a good way to ensure you’re not overthinking the issue. Use them!

Summary

In the end, it’s not about how many lines of code you can write, neither about how fast you go through this current development cycle. What makes a good coder is how good they prepare their code for the next issue, and the next and so on. Therefore, in the long run, you are way more productive if you spend some time today to make your work easier tomorrow.

Do you know any other useful metrics that a programmer should use?

How to write better code – 6 things I did not found on the first page of google search results

I know there are millions of answers to this question (I for one get 484,000,000 hits on google when I search for How to write better code). They all seem to revolve around the same general idea, so I will not be repeating that. You are free to do a google search yourself and read through those.

I will however, be sharing some tips that I picked up that you may find useful. Here we go:

1. Do not compromise

We all know when we are doing a hack, or a nasty work-around because the right way takes too long. We know it, and we still do it. Maybe it’s late in the day, and we just want to go home, maybe the deadline is closing fast and someone is going to be really mad if the project is not done on time or maybe we just can’t be bothered to do things the right way. My advice to you here is, you guessed it, do not compromise code quality. Compromises made now will slow down development in the future, which will in turn lead to more compromises and so on and you will end with a mess of a project that you will need to maintain. And that’s no fun 🙁

2. Less is more

A programmer’s proficiency is not measured in the number of lines of code they write, it’s more like the opposite. The more code you write, the more chances to break said code. The odds are against you. If you have to think twice as long on task to write half the code, do it, it totally worth it.

3. DRY

Don’t repeat yourself! I’ll say it again: Don’t repeat yourself! (pun intended). In my experience, the worst codebases I had to deal with are the ones which chose to implement the same logic in many (and often quite creative) different ways. Don’t do that to the person maintaining your code. If you have to use the same logic in two different places, be sure to have the code in one place.

4. Follow the existing pattern

This is an extension of the previous topic. Please try to avoid adding a new pattern to the project, unless it’s really necessary. One style of doing things should be enough for one project. I accept that this may be hard. Especially if you are just starting on a new project that seems to be doing things differently from what you are used to, but don’t give into the temptation. Keep the project simple and follow the existing pattern, even if it means more work for you now. And remember: It will get easier with time.

A notable exception to this rule is the process of replacing the existing pattern with new and improved one. If you are doing that you can have two patterns in the project at the same time, while the old one is slowly retired.

5. Automated tests

I really don’t see nearly as much as these that I would like to. If, at any point in the development process, the thought that you need more tests crosses your mind, you are probably right. As you might have guessed if you follow my blog, I’m a big fan of automated tests (maybe enough to write an article about them some day). Automated tests are great, and they have many advantages to them. They give you the confidence that what you just changed did not break any hidden functionality. They serve as an always up to date documentation for the project (as long as they are passing).

6. Common sense must prevail

We need to take a step back now and acknowledge that each and every one of the points above can be taken to extremes. As you might expect, taking internet advice to extremes is not advisable. So I leave you with this: Whatever you do, don’t forget to use your basic, human, common sense. You are free to bend and adapt the rules so that they make sense given the situation.

Dealing with legacy code – the bane of our existence

Unless you are really, and I mean really, lucky, you are bound to be stuck on a legacy project with a thousand-year-old codebase. Maybe not a thousand years, but close enough. If it’s any like the code I have been working on, it’s big, really big, it’s complicated, no one really understands how it fully works, and it’s riddled with anti-patterns. What to do?

Burn it to the ground and start fresh

So, delete the codebase and re-write everything in a nicer language, with design patterns and all the bells and whistles.
Phew, that was easy, we didn’t we do that from the beginning?! Problem solved! Let’s get back to checking Reddit now!

Getting back to earth

OK, so burning everything might not be a sensible solution. More often than not the code took years upon years to write and is worth a small fortune. The client is not just going to let you throw that all away and sit tight while you are starting again from scratch. You’re in a bit of a pickle, you can’t live with the code and you cannot get rid of it. What to do ?!

The only sensible solution at this point is to slowly improve the codebase. It’s a slow and time-consuming process, so don’t expect your life to improve overnight. We now have a plan, let’s see how we go about putting it into action ?

Test it, test it, then test it some more

You will need to improve the code in the future, however, it’s really hard to do that when you’re unsure if you have broken it. This is where tests come in. Unit tests, integration tests, performance tests, you name it, you probably need it. The more tests you have, the more confidence you have that the changes you made are not breaking anything. In addition to that, writing tests help you better understand what the code is doing. Hooray for you!

Isolate and control access to the old world

The plan here is to control access to the old code as tightly as possible. You can do that with nicely designed interfaces and wrapper classes. Here where the tests we previously talked about come into place. Once you know you’re not breaking functionality, then you are in a position to do some serious refactoring. Every new functionality you add should use one of the nice interfaces. If there are none, add them! If it suits your project, you can turn the old world into a web interface that can be safely isolated from the shiny new code you are adding.

Slowly retire the old world

Since the legacy code is now contained and you can control access to that dusty old thing, as time goes by you can gradually remove the calls to it. Once there are no more calls, that is when you are ready to remove that chunk of code. Do that over time and the big monolith you begin with is starting to look smaller and smaller. You may not be able to fully retire the whole legacy piece of code, but you can at least save your team the trouble of always having to work with it.

Slow and steady wins the race

I must warn you, this is NOT a quick process. Depending the size of the legacy code it may very well take years to make a dent on the original codebase. You need to exercise patience. The motto for the whole process should be Leave the code better than you have found it. You may be tempted, especially when the deadlines are tight, to cut corners and go for the easy, messy solution. Do not do that!! I’m saying it again: Do. Not. Do. That. This is how we got that big mess we started with. You cannot solve a problem by doing the same thing that caused the damn thing in the first place!

 

How to deal with issues in production – the short version

Okay, imagine the following scenario: You’re happily going about your day, coding away, without a worry in the world, and then this hits you: “There is something wrong with the production app” (well, maybe not this exact message, but you get the idea). What do you do?! How do you go about fixing the problem and go back to your everyday life?

1. Calm down

Yes, things are bad at the moment. However, this is not the time to lose your head and run around with your hair on fire. You need to have a clear mind in order to focus on the problem and easily find the solution. Go to your happy place and come back when you are ready to get to work.

2. Calm down some more

If you are anything like me then you probably are not calm enough to think clearly. You should calm down some more.

3. Investigate the issue and understand it

OK, now we are ready to start doing something about the problem. The first step is to understand the root cause of the issue. Maybe someone deployed something they shouldn’t have, maybe the data became corrupted or maybe, and this only happens once in a blue moon, maybe there this is just a misunderstanding and there is no issue at all, in which case: Congratulations, you solved it.

At the end of this stage you should be able to at least answer the following question: When exactly does this happens ? 

4. Estimate the impact

Now that we know when the issue occurs, we can start thinking about who does this issue affect. A quick and dirty estimation should be enough. Some useful classifications are: critical (everyone is affected and everything is broken), medium (there is some impact, but the project will probably survive), low (this issue affects only a handful of clients/users), minimal (yes, there is an issue, but unless someone looks really, really hard they’re going to miss it).

Based on the impact of the issue you need to decide if you should fix this now, or later.

At the end of this stage you should be able to at least answer the following question: How bad is it ?

5. Calm down some more

OK, things are starting to clear up a bit now. You know what the problem is and how bad it is. Take a deep breath, decide when this needs to be fixed: either now or later.

6. Track down when the issue was introduced

If you use a versioning system (if you don’t you really, really should), use that to track down the commit that caused the issue. Look trough the code and use a sandbox environment (if any available) to figure that out.

At the end of this stage you should be able to answer the following question: What is the exact cause of the issue ?

7. Fix the issue

Now that you know what causes the issue you are in the best position to start fixing it, so go do it, be a hero and save the day.

Congratulations!! You fixed it!  You can go back to your happy life – crisis averted.