Technical project management is an act of balancing. There are many interests. Some interests are inherently increasing product quality: someone may want to spend more development time for unit testing and documentation (to improve long-term maintainability), others want to spend more development time for small UI details striving for amazing user experience, yet another person might demand more time spent on manual testing, increasing testability and establishing clear operating procedures. There are also other interests that are often perfectly valid, but objectively they work against product quality, for example trying to spend as little time and budget as possible, trying to have so many features “done” as possible before an important deadline, or changing project team on-the-fly to balance company-wide targets.
Balancing these factors is not a simple task. And this was an example of a perfect team and perfect project! Sometimes things are less than optimal so that you might have additional out-of-scope factors, for example power struggles, striving for promotion at any price, or just persons enjoying their off-work life as much as possible and contributing only so much to avoid being fired.
Given that this balancing is a very non-trivial task, it is more art and intuition than logic and science. This opens a possibility for disagreement.
Once, I was technically managing one big project. I knew we were understaffed, and asked for more staff for very specific areas. I knew I wouldn’t get what I wanted. My guess was that I’ll get another person, a colleague of mine I had chance to work with in one of the previous projects. I didn’t like his approach of how to balance projects, even to the point of asking myself whether he can balance projects well. He was equally opinionated about my skills.
If he was to be added to my team, I saw a high possibility of disagreements about what development activities we’re going to spend our time on. I knew that this is an inter-personal problem and as such it must be handled in a personal discussion(s). But first, I’m not that good in soft skills. Second, it was a hot phase of the project and I was very busy with other, equally important things. So I’ve slacked and written down a “memo for new team members”. I was hoping to use my authority as software architect and the oldest technical member of the team to force my colleague to comply with my principles.
I was on the verge of defining a software development process for the only reason of avoiding a personal discussion with a team member.
This is embarrassing. Fortunately to me, this colleague was never added to my team, so I’ve never used this memo against him. And several months later some other unrelated issues have forced me to part ways with the project team and thus to avoid solving this problem altogether.
When I’m reading the memo today, I feel it is still useful though. First, it reflects pretty well my philosophy about how to create the very first iteration of
risky innovative big software systems. Second, its language reflects how frustrated and helpless I was at that time, and it is so cute. Therefore I want to share it with you. Here it is.
You are going to be assigned to work with the %customer_name team. Please take a minute to read this memo.
Everybody in the team is a highly competent professional; therefore we generally avoid establishing any fixed rules that may never be broken. Nevertheless, we must align our initial assumptions to ensure the best possible software quality and minimize all kinds of risks.
If you want to enter the team you have to agree with the following:
I. You will not assume that you have the exclusive ownership over the source code of “your” subsystem.
a) You will use any tools or extensions not included in the standard developer PC image, only if their usage it absolutely necessary to accomplish the task. Reason: if another developer will have to develop the subsystem, it might be too expensive or, in case of a very high time pressure, simply impossible to install those tools to be able to work on the subsystem.
b) And vice versa, you will not threat other subsystems as external entities or black boxes. Instead, you will actively read and understand their source code to understand their real behavior. Reason: if another subsystem doesn’t work as specified in the interface documentation, you are the one who has the best chance to understand what’s happening, because you might be the only person in the team working in this area, keeping all the factors in the short-term memory and having a reproducible case.
c) It also means you’re responsible for fixing bugs in those external subsystems (if you’ll happen to find one) or change their behavior if needed for your subsystem. Speak with the primary developer of the subsystem first, though. Reason: often it is easier to fix software directly instead of communicating the problem and then waiting for its solution.
d) The fact you’re primarily developing one particular subsystem doesn’t mean you will never develop other subsystems. You should know the “bigger picture”. Reason: you should be able to replace other team members in case of emergency.
II. You will not assume that you have control over concrete deployment and usage scenarios on any server except of your own development PC.
a) You will never ever include any production configuration files into the source control along with your source code. If your system will have a test server and you opt to include test server configuration into the source control, you will explicitly threat them as template / reference / sample configs and will not create any release scripts automatically overwriting the actual test server configuration. The re-configuration of the test server and all production systems will be performed directly on the systems. Version control of configurations and backup responsibility lies on persons performing re-configuration, and on system administrators. Reason: the software system will be operated by %customer_name. By reflecting that in our processes we make our environment more realistic. Besides, other persons will be able to re-configure the test server without having to get the source code of your subsystem.
b) You will split the system into separate applications. When feasible, one must be able to operate each app manually. In case when output of app A will be directed to the input of app B, the data exchange format must be kept as simple as possible, and it must facilitate manual creation (i.e. creating the input of app B without using app A should be as easy as possible and not limited in any way). Reason: in case of emergency or extreme time pressures, we want to be able to reconfigure planned data flow and hack some of its stages manually.
c) You will not validate any input upfront, except otherwise is explicitly specified in the requirements. Instead, you will provide meaningful exceptions in case the input is not correct. For example, if your input will happen to be a string, and some later processing stages of the app will have to threat this string as integer, you will keep the string being string up until the place where you need an integer, then attempt to convert the string to integer and, in case of conversion failure, you will throw an exception with a helpful message. In case your subsystem will be configured to avoid the step requiring converting the string to the integer, this string input must not be validated, unless explicitly stated by requirements. Reason: your software must be usable even in the situations you cannot envisage right now. String in a format you consider invalid today, later on during the operation stage, or in case of emergency, might be reconsidered to be a valid value, depending on which processing stages of your app will be turned on or off in the concrete usage scenario.
d) In case your software will store data persistently, you will keep the data format simple so that it will be possible to change the persisted data manually. Reason: simplifies debugging and also fixing the system in case of emergency. For example, all things being equal, you should prefer to use MS SQL field type “Identity” over Guids for the ID field, because it is more time consuming to generate the latter when inserting a new row manually.
III. You will not assume that the subsystem is a long-living project.
a) You are allowed to write unit tests, but you will not try to cover most of functionality with unit tests, and you will not write a documentation about how the system works (except when explicitly required by the customer). Reason: In case of short-living projects, there is a better strategy to keep the subsystem maintainable: make it readable by keeping its parts extremely simple, and avoiding unnecessary classes, methods and lines of code.
b) You will avoid using frameworks and software development patterns (for example IoC, MVVM, etc) except when they really needed (i.e. would greatly reduce development time or increase security). Reason: each used framework and pattern might require additional time of other developers to understand it. The benefits of frameworks and patterns can be only achieved if the same team is working on the same subsystems for months and years, so that initial time investment in understanding the framework will be amortized over the long time. This is not our case.
%customer_name team wishes you (and all of us) a pleasant journey. Welcome aboard!