Preparing for your first job out of college in C#
This is an article walking through the differences between coding on your own to finish class projects in college and coding for an employer or on a team. The code for the article can be found at: https://github.com/BrettKoenig/BeginningCSharp. There are some assumptions being made in this article:
- the target audience is developers with 0–2 years experience
- assumes a passing familiarity with .NET development (eg. basics of visual studio, nuget, C# )
Since this is an article on the real-world software development process to help prepare you for your first development job coming out of college, the best way to accomplish this is to do a case study. I will start by writing the project how I would have done it in school. Then throughout the article restructure the program so it could be deployed into the wild, and be acceptable to the rest of your development team.
What will we be the end result? We’re going to write a simple program that will download attachments from a Gmail account, read some information from those files, print it to the console, and then store the files on Amazon’s S3 service. This problem touches on a lot of helpful areas with external API’s, file system, and file I/O. Hopefully this is a semi-common and general solution that can be useful to many people. I would encourage you to pause here and take an hour or so to accomplish this task. Here are a couple of tips to help get you started:
- For the Gmail portion:
- Turn on Imap in Gmail. Go to the settings gear in the top right when you open Gmail, click Settings, go to the Forwarding and POP/IMAP tab, under Imap Access click the “Enable Imap” radio button.
- You’ll also need an app specific password for accessing your email from the code. Visit this link for details on how to do that https://support.google.com/accounts/answer/185833.
If you don’t even know where to start, open Visual Studio, then go to the File menu, New, Project and in the new pop-up window that appears under Installed->Templates->Visual C# choose Console Application. Choose an appropriate name and a location you want to save it to. Click “OK” and go from there!
Before You Start
Get to know your environment. If you will primarily be a C# developer, you will most likely be working in Visual Studio. If so, see the General Tips/Tools For .NET Developers section at the end of the article. No matter what language you’ll primarily be working with there are a few things that are essential early in your career:
- When you’re coding, use source control early and often. This could be git or svn, but this can really save you if something goes wrong down the line. Not only that but it also gives people the opportunity to look at what you’re working on and allows you to easily take a quick look back at how you were doing something previously if you change your code somewhere. Stop doing the “Save As…” a different name and in a different location approach and just use some kind of source control.
- Learn keyboard shortcuts. It will make you much more efficient and can save you frustration of some monotonous tasks.
- Learn how to systematically debug a problem in your environment. You will have to do this a million times throughout your career. You are a very valuable developer if you can debug issues well.
- Learn coding standards for the environment you will be working in. In this context I’m using “environment” as a term to encompass: language, technology stack, company, and project.
- Seek out mentors! This is essential for developers of all ages. If you have an assigned mentor, start with them, but it is still advisable to seek out additional mentors. Ask your mentor(s) for a prioritized list of what you need to know to succeed in your role. Come back to the mentor when you hit roadblocks along the way, or to get constructive criticism/feedback.
Give It A Try
Read “The Problem” section once more, and give it a shot before continuing with the article.
Awesome! I’ve finished the first round of the project. For me this looks like a 111 line file that handles the whole problem. I’m excited because I got the project done way before the deadline, so I take it to my tech lead to review. What are some things that we could review or modify to improve it?
- Take constants out of the code. This is a pretty quick and easy change and will position us for easier testing, or easier modification of our project for future developers.
- Use better variable names. Don’t be afraid to be verbose. Explain what the variable is or what you’re doing with it when you instantiate it. Also, as an aside, you will be tempted to use the var keyword when you initialize variables because it is easy to make it work and errors seem to just disappear. If you know what type you are expecting for that variable explicitly name it to improve code readability. There is a debate in the development world if you should comment code, or write code well enough that it is obvious what is going on. Explicit variable names can take you a lot further towards accomplishing the latter, but comments don’t hurt either.
- Think through the solution. Don’t just take the business requirements and do them step after step. Look for areas where you can optimize performance and utilize your creativity as a developer. Like I said previously, not everything we learned in college is useless. Think back to big O notation. The current solution is roughly big-O of 3n. We can do much better.
- Separation of concern and single responsibility. I am essentially violating each of the five of the SOLID principles. To make a change to this, someone would have to read through the entire code base to find what they’re trying to change and make sure what they’re doing is ok and won’t break the entire solution. Changes should be able to be made more obviously, through naming conventions, different classes, interfaces, and methods.
- How could we ever test this? Other than running through the whole process and sending emails with attachments, making sure that it hits the database and gets uploaded to S3, we don’t have a good way to test it. There is no concept of unit testing, and as it stands never will unit testing.
Could anyone reuse the code? If you rolled off of that project, could someone else make quick small alterations to it? Hopefully, by covering the above bullets it will be obvious how we can significantly improve this, and minimize the amount of caveats and catches to someone else picking up and running with your code.
Writing Cleaner Code
In this step, we are going to take care of a few simple steps that are obvious after taking a look back on the solution, and thinking about it a little bit. The goal is not to get something working as soon as possible just to get it working. As developers, we want to craft solutions and assert creativity. We don’t want to just use brute force on every project. There are three things that we would like to make changes to in this step; constant variables, variable names, and workflow.
In school, when you are going to work on a project for one week by yourself, turn it in and never think about it again, having constants in a couple different places in your code probably isn’t a big deal. Additionally, it may not have been worth the time for you to look up a way to approach it differently, and knew that you could just define a string inline. However, there are multiple reasons this is an ugly approach in the real world. First, if someone wants to find where you define these constants, they have to scroll through your entire code base. Furthermore, if they want to make a change to one of them, they need to potentially change it in multiple spots, and make sure they’re not missing one. Take our temporary file path for example. In the STEP ONE CODE, we have the same local path in line 26, 35, 76, and line 88. In this small example it may seem trivial to change it in four places. However, as the project grew this issue would multiply. We will make changes so that this value is pulled from the same location. A change can be made in one spot; thus, minimizing the risk for typos or missing of the places where it is in use.** **This is also useful in a maybe not so obvious way. If you define your variables in an App.Config it is much easier to change those variables for different environments (development vs production). Specifically we are going to put the email username and password, the email username that we’re searching for attachments from, the temporary path to save the files at, the database connection string, the AWS bucket name, the AWS access key, and the AWS secret key into the App.Config.
Next, we will change the variable names to make them more useful. In this example, I made them excessively poor to make a point (don’t ever name a variable “thing”). There is a pretty big debate among developers as to whether a developer should add in comments, or write the code descriptively and clear enough that it is trivial to see what is going on. I’m not going to take a stance on that one way or the other, and will leave it up to the guys that have been in the industry longer. However, variable naming can go a long way in achieving the latter. Use variable names to describe what is going on, what the variables represent. Don’t be afraid to be verbose, or descriptive with your variable names. Furthermore, try to avoid using the same variable names in the same file at different locations (like we did in the STEP ONE CODE at line 35, line 76, and line 88 with “thing”), even if the scope is different and types are the same. This will help with clarity and code readability. Here are a few more simple steps to take with variables:
- Don’t instantiate variables like “string foo;”. Instead use “string foo = string.Empty”. This also helps with readability.
- Declare variables in the smallest scope that you need them.
- You don’t need a counter as frequently as you think you do.
Finally, think through the solution. In college, it is really easy to get a list of three or four “business requirements” and write code that just does those one after the other without taking a little bit of time to think about what the code is actually doing. Then you end up with code like I had in the step one code, maybe your solution is already better than that. Think back to when you learned about Big O notation (if you are not sure what I’m talking about click here. It is a way to time your solution). Right now, my solution is big O of 3n because we’re traversing over each file three times. We could easily do each of these processes in one loop and improve our solution to big O of n. Your employer won’t be paying you to be a robot and just output the requirements. They want you to think through what you are doing and add value to the work you are given. Look for places where you can make small improvements like this in your work day-to-day.
These changes may be obvious, trivial, and may not take working as a developer professionally to realize. However, they are a few things that I don’t always think about or do in my code. Try to keep these things in mind while developing. Make these changes in your code before continuing if they are not already in place.
Now that everything is just a little bit cleaner, we are going to start separating things out. The hope behind this is that we would make our code base a bit more maintainable, cleaner, and a little closer to adhering to some of the principles of programming. The big advantage to separation and designing to an architecture is that it will help other developers understand your code better, ease maintenance on your project, and eventually ease development for you. In college, these weren’t necessary because often you may be the only one working on a project and you may be only working on it for a week or two at most and have no need to maintain it after that. Coding for a company is quite a bit different because if you are adding value, hopefully your solution will be used going forward for quite some time. The odds are high that someone else will need to look at your code at some point in time either to debug it or add a feature to it. One common complaint you will hear is of developers that had to work with “legacy code” and how much of a pain it was because the original developer didn’t write cleanly. By separating out our code into different classes with interfaces and adding in some architecture type design we can hopefully ease the burden of anyone who would find themselves working on our program in the future. It will be worth your time to look into a couple basic architectures like onion (hexagonal) architecture, or patterns like the decorator, factory, or adapter patterns as these are commonly used in real-world development. Knowing these patterns and when to use them properly will give you more context when you hear someone talking about them in the workplace, and will make your code higher quality.
First let’s add a Class Library project to the solution, naming it “BadExample.Service”. The benefit of this will be that we are doing a decent amount of things that other people around our organization may want to do as well (such as getting attachments from Gmail, or uploading to S3). By separating these methods into a class library, it will allow others within our organization to easily reference or reuse the code that we have already written. By essentially “wrapping” our class library with a console application, we can make the class library do our specific objective with the correct parameters. We will move the logic of our code from the BadExample console application to the class library, and use the console application as an entry point into the class library. To use the class library in the console application we need to reference “BadExample.Service” in “BadExample”. To do this right click on the “BadExample” project in the Solution Explorer window, go to Add then click Reference. Under Projects, Solution, check the box to the left of BadExample.Service. Rename the Class1.cs in “BadExample.Service” to EmailAttachmentProcessor.cs. Copy the code from Main in Program.cs to a new public void method called ProcessEmailAttachments and copy the GetCredentials Method into your EmailAttachmentProcessor class as well. You should now uninstall the Nuget packages from your console app and install them to your class library and add in all of the references.
Notice how we now have a console application that runs a method inside of a class library. Now if someone wanted to do something similar but with different constants, or add a little more functionality to it they could by just wrapping our class library. This is good, and adds some flexibility. But we could add even more by breaking down our code into more service classes. This will help us to adhere to the single responsibility principle, and make our application more testable. There is no exact way to break down classes, but the way I see this we could have a class that does our email retrieval and processing, a class that does our file handling, a class that does our database calls, and a class that handles our Amazon services. Let’s create those.
We have created our services. All of the sudden, our EmailAttachmentProcessor class is very readable and you can see exactly what we’re doing. Sure, maybe we could do more separation. But we will see how it goes as we continue development, and get into testing. You will notice when we get into testing that some of our issues will be exposed and we will need more classes to make things testable. We will continue by adding interfaces to these classes. Interfaces will allow more flexibility in our code, and it can ease the development for future developers. Additionally, if someone wanted to reuse our class library we will make them much happier if we have some interfaces they can implement.
Take note of the project structure in the Solution Explorer at this point.
Now, let’s go ahead and add some folders just to make things a little quicker to find within the solution. Once again, we only have a few different files but if we had a larger project or solution this would become much more helpful. A new folder will have a new namespace, so you will have to rename the namespaces and include a new using statement wherever your services are implementing interfaces, according to the new interfaces’ namespace.
A term you will most likely hear frequently is “IOC container”. “IOC” stands for Inversion of Control. An IOC container, is an easy way to substitute different class implementations of interfaces. Using an IOC, you will only have to change your code in one spot. It’s intimidating to hear, “Are you using an IOC Container?” or, “Where are you setting up your IOC?”. But it is actually easier to use than it sounds like it would be. Go ahead and get the nuget package called autofac. Try looking at their documentation and implementing it yourself.
Within the first couple months I’ve had to do one thing frequently that is often talked about among developers and product managers that I did not have to do once going through school: Testing. The way the project started (all in one Main method) there was not really a way to test it other than running through the entire scenario; which in college, may be acceptable. However, unit testing is a must in the real world. It is the only way to come even close to the different inputs you will see in a production environment. A lot of the previous steps we have taken such as pulling our constants out into an app.config and separating code into different services have landed us in a very nice position for testing.
Testing can do so much for your development process. It is a vital step before pushing any code in any kind of monitored testing environment. It can validate you among other developers and can harden any vulnerabilities in your software and minimize any risk your software may be exposed to in the World Wild Web (see what I did there?).
The first time I sat down to write unit tests, I had no idea where to start or what to do. I had to sit with a few coworkers for multiple hours and still felt like a novice. My goal of this exercise is to equip you to write your first few unit tests in .Net and at least give you a first step in the right direction to not be completely clueless on your first project.
Let’s write the first few unit tests for our small sample project:
Add a class library project to the solution called solution name and “.Test”. Replicate the structure in your .Service project. Install the xunit, xunit.runner.visualstudio, nsubstitute nuget packages. Add a reference to your Service project in your .Test project. For each class, we can add a class with Test appended to the end. Let’s start with the EmailClientService. A reasonable test for this would be to make sure you can or can’t login, and that not anybody can login. For our first test, let’s make sure that garbage credentials don’t log in successfully. This already exposes an issue in our code. This may seem frustrating, but it is a good refining process! If you look into our EmailClientService, we have the login logic in the constructor, but we have no way to know if that fails or succeeds. If we break that out into its own method, we can get a return value from it and test just the login. Try passing bad parameters to login in a test method and get your test to pass. I have my first test passing, and you can see it here:
Obviously, we want to test more than just bad parameters failing login for this application. That would be some pretty low standards. Once again, since this is such a small application. It’s easy to see that we haven’t covered very much of our code with our tests. But as a project scales, a very valuable measurable is how much of your code is being tested.
Go ahead and download OpenCover, ReportGenerator, and xunit.runner.console nuget packages for your entire solution. You can copy the coverage batch file in my solution and make sure the paths exist, also change the name from “BadExample” to whatever your solution is called. After modifying the batch file, save it. Navigate to it in windows explorer and double-click it to make it run. After running, it should create a coverage folder. Inside of the coverage folder, double click on index.html to open it and view it. On my first run, I only have 9.6% of lines covered. The goal is to get close to 100%. Obviously, there will be some things that you just can’t test. There will also be some things that just do not need tested. For example, in the EmailAttachmentProcessor.cs there is not much to test, because that is just calling out to our other services. In those moments, decorate your method or class with an “ExcludeFromCodeCoverage” attribute.
What you will notice is that unit tests, and code coverage will really start to harden up our code. My previous testing was just to run the app from start to finish and see if anything breaks. There are a lot of apps that this is not even possible with. You may be working on a small portion of a larger project, or maybe you’re working on a website and not necessarily a console app, or service that runs step-by-step. Unit testing is a must as you move into larger projects.
If you look at the solution as it stands, it is a little bit different than the STEP FIVE CODE. I went ahead and built out my unit tests, made changes to make my code more testable, and various other alterations. These changes were either repeats of what was stated earlier in this blog, or didn’t necessarily fit into a single point. However, I encourage you to look at the solution and compare it to the step 5 code. Specifically, look at how I handle constants differently in the two steps. Then compare the current solution to the STEP ONE CODE and see where we have come from.
Code for the person you are going to be six months from now. I realize that this example was very small, and underestimated the coder coming right out of school, but the principles can be applied to much larger projects. All of these steps will help you to make a more readable, cleaner solution that will be much appreciated by you if you need to go back into the code at a later date, or by another developer that comes along to support or add functionality to your code down the road. By avoiding complex solutions you will enable much quicker debugging. Instead of having to read through all of your code to figure out what is happening, someone can jump right in for a quick fix! Before calling a solution “complete” do as we did in this article. Go back through your code and try explaining it to someone, or a duck, to see if there are opportunities to simplify your code.
It goes back to the, “treat others how you would like to be treated” mantra. If you sit down to a new project would you rather be faced with the monstrosity of the project in STEP ONE CODE or the more elegant solution, how it stands today.
Last, relax! You’re going to be OK. Your employer won’t expect you to show up on the first day and know everything. You will always have things to learn, and time to do it!
General Tips/Tools For .NET Developers
Below are some general tips, or topics to look into before starting your first job or internship. They will help you be a better programmer, or help you have better conversations about programming.
- Each solution in Visual Studio can have multiple projects. This will be obvious to anyone who has been developing in .Net for any amount of time, but can be non-obvious and can cause confusing conversations due to semantics. Be careful about saying what you want do with your “project” if you are referring directly to your code, because it could be an ambiguous reference between your project and your solution.
- A .dll is a compiled class library. To find the .dll, navigate to the location of the directory of your project inside your solution in your Windows Explorer (Fun Fact: You can quickly do this by right-clicking on the project in the Solution Explorer window, shown above, and choosing “Open Folder in File Explorer”). Then choose the bin directory. Depending on if you built the Solution in Debug or Release mode choose the appropriate folder. Inside of that folder you can find the file, it will be YourProjectName.dll. This is useful if you have classes/methods that you want to use in multiple projects. If you find yourself using it extremely frequently, think about publishing a NuGet package using the .dll. I have found this useful, and have had the opportunity to publish an internal NuGet package for a corporation that was a wrapper on top of the AWS SDK NuGet package because they had been archiving files to S3 in multiple different initiatives. It wound up shortening the code from about 15 lines in every solution they wanted to upload a file to two lines, initializing a new AWS class and calling the upload file method with appropriate parameters. This also will help with maintainability and separation of concern.
- When you start a new project, be aware of what framework version you’re targeting. You can check this by right-clicking on any project in your solution and clicking on Properties. Visual Studio will default to a newer version of the .Net Framework. Know about the environment you’ll be deploying to and what features of .Net you will actually use to determine what the appropriate version of .Net would be appropriate. The danger is that you would target a higher version of .Net than your server has installed on it which would cause issues. As we sit in early March 2016, it is probably safe to target .Net Framework 4.0 or even 4.5. But it is something that deserves some though when first starting a project.
- Resharper is a great tool which “provides hundreds of quick-fixes to solve problems automatically”. It will help you identify common errors, adhere to basic conventions, and give additional keyboard shortcuts. If you’re developing in Visual Studio I would strongly suggest using Resharper as a productivity tool.
- Keyboard shortcuts outside of Resharper are another thing that will improve coding efficiency. It will help you find code throughout your solution quickly instead of wasting time scrolling or searching through your code to find what you are looking for. Becoming proficient with keyboard shortcuts will also help validate your skill among other developers.
- Leverage IntelliSense.
- When using Nuget, 99% of problems can be solved by running “Update-package -reinstall” in the Package Manager Console in Visual Studio.
- Run Visual Studio in Admin mode. If you’re not running VS in Admin, you may run into some funny permissions issues that could be easily resolved by running Visual Studio in Admin mode. Learn how to do that here.
- SQL Server Management Studio or SSMS is a tool on Windows machines to connect to databases. You can view the data, and run queries on them through SSMS.
- Variable names should be camel-case starting with a lowercase letter, method names should be PascalCase. That is typical convention. However, your company/project may have an established set of standards. Try to follow them in that case, to ensure your code is easily readable by other developers.
- How to be a Better Junior Developer
- Big-O Notation
- Overview of Object Oriented programming4 Pillars of OOP
- Difference between Interface and Abstract Class
- Object Oriented Programming
- 3 Coding Principles
- N-Tier Architecture Model
- C# Language Overview
- Visual Studio Debugging
- Visual Studio Keyboard Shortcuts
- ORM (Object Relational Mapper)
- IoC (Inversion of Control)
- Useful Tools
- Package Mangers (nuget)
- Git vs. SVN
- Common Git Commands
- Git Branching Technique
- Unit Testing