Thursday, July 27, 2023
13 years old girl
Monday, November 21, 2022
Magic word for Software Architect (and others too)
Moving to the architecture side of the programs made me ask a simple question all-day long: "Why?"
Most of the time I get reasonable answers, some developers actually know better than I do what is the problem they solve. From time to time there are good discussions which teach me. When a discussion which teaches me happens it means the day was extremely profitable! The problems arise when the answer is awkward.
In some cases, rare ones, I received an answer beginning with "it's for..." followed by some unrecognizable sounds and a quick "I'll call you later" hang-up. The code on the 2nd call somehow was lighter, more efficient and readable.
In most of the cases I get an answer like "the book says it", or "I'm used to" or "why shouldn't I use a time-wasting algorithm on a fast computer". Usually it happens when the developer is a fresh developer out of the academy or whatever they call it. Those are the most hard to handle people, too much theory not enough field practice. The fact 90% of the people wear watches on their left hand doesn't mean the other 10% are wrong. Same goes for using some design patterns. Separating the company to 3 different environments (DEV, QA, PROD) is usually a good practice, but creates extra efforts if there are separated programs in each section. The effort multiplies if each product has its own set of testing environments. The end result of not separating it correctly is "job security" for the poor DevOps.
All those problems could have been solved, or at least handled a priory by asking the simple question "why".
"Why do you separate the environments by product instead of work mode?" Is it because the company works this way or because it's coming from a book? Does the permissions model come from a business requirement or is it a "nice to have" feature equivalent to digging random potholes in the floor? Same goes to the communication protocol used, did you choose something according to requirements or according to tech-department's amusement? Left on their own techies use the latest technology possible, but it might mean the older technology pieces won't work. My new DELL computer doesn't have any legacy USB ports, you can guess the annoyance it causes when the company supplies only USB headphones.
When it comes to development the first "why" is regarding the framework - programming language, source repository and build machines. Companies which have some infrastructure already built are exempted from this, the cost of moving from TFS to Github is huge for a mid-large company. When you have the infra in place it's usually better to continue with it unless other issues intervene. For a new company or a small one the question is different. Switching a small project from one infra to another, regardless of it being the programming language or build machine, "cleans" it from the extras accumulated over time. That code which was added at 2AM during "the caveman hunting feast" crisis, and suddenly fails due to an invalid reference, in this case the "why was it added" is clear, but "why wasn't it removed yet???".
Sunday, February 14, 2021
The difference between a developer and an architect
Friday, May 29, 2015
Debugging 101
Let me begin at the conclusion - use your experience. Most of the times when you have enough experience you can go directly to the source of the misbehavior and fix it.
This post is about the how to get the experience.
First thing to do is to understand the bug. Easy? Not at all. Just the other day we got an error stating "the dates are incorrect." after trying to explain to the user that created date, end date, and effective date might defer we've found out that the user had a totally different error. The original error was missing data, the description for the bug included so many misleading directions that we got lost on the way. Luckily, experience taught us to ask first.
Once you fully understand the bug, it's time for the second step - reproduction. Now this thing might be hard (or nearly impossible when it's a stress related bug), but it's by far easier to debug on your machine then on the lab 5 floors below ground, next to the "devil" called lab-manager, and his "minions" co-workers.
Note that in some cases the only way to reproduce is by using a non-development machine. While not being as easy to debug, it's still a good reproduction.
Now that you managed to figure what you want, and where to test it, we can advance to the third step - checking the code.
It's important to understand, before trying to debug and fix the code, check it. If there's a candidate for the bug's location, mark it (or add a log line to verify your assumption). Note there might be more than one location, mark them all, it saves time.
Step four. Now it's time for the real debugging. After we finished the setting up we run the scenario and verify the preliminary assumption. Hopefully we've caught the culprit, in case we haven't, return to step 3, there are more locations which are hidden.
Step five - the fix. This is the most dangerous step! Fixing a bug is prone to opening a new one in an unrelated location. Assess carefully the impact of the change, consult with others, and try to explain to someone (even your dog) what you are going to do. Usually we do shortcuts on this step, especially when the clock says 2:00AM, but it backfires at us always.
If all passed successfully, we're good to go. Call the QA, say we fixed it, explain the impact, and let them do their magic. Hopefully no new issues arise and we can ship it out.
A quick example. I'm using this code as the "buggy" program.
1 class Program
2 {
3 private class HelperClass
4 {
5 public List
6
7 public void PrintStrings()
8 {
9 foreach (var str in StringsList)
10 {
11 Console.WriteLine(str);
12 }
13 }
14 }
15
16 static void Main(string[] args)
17 {
18 var helper = new HelperClass();
19 helper.PrintStrings();
20 }
21 }
The user would come to us stating "IT DOESN'T WORK!!!"
This code can't work, it's lousy and if someone would give it to me, he'll find the way out within the next minute. But "doesn't work" is not the error it gives. "Null reference" or "object reference" is the error message we're expecting to be given by the user. After we'll see the demo of what he did, we'll have it.
Step 2 - reproduction. In this case it's easy, run it on the developer's machine, it will fail.
Step 3 - find the candidates, in this case it would be lines 19,9 and 11. Either print a log or put a breakpoint on those lines.
Step 4 - debug. The debugger would stop on line 19, afterwards on line 9, but line 11 will never be reached. Further investment shows line 9 fails, StringsList is null.
Step 5 - fix. In this simple case the fix might be adding
helper.StringsList=...
or checking for null before the printing
if (StringsList==null) return;
or surrounding with try catch (bad idea, but fixes the bug).
Once we fixed the code, it's time to deliver it. Now the user can check it on its own.
Tuesday, December 10, 2013
Inheriting Someone else's Code
The following issue is the separation, or lacking of it, to different files and projects. While being a little nuisance, it's a "don't touch unless you have to".
The major issues arise when coming to fix a bug. What calls what? Which event comes first? What function threw the exception? All of those questions can be answered easily when you wrote the code, though it might take time to refresh your memory when it's an old piece. When it's not your code - spell trouble.
The easiest solution is using the debugger. There are 2 issues with the debugger:
1. Race conditions hardly ever happen when the debugger is attached.
2. Most of the clients I had didn't want the debugger, didn't allow it, and in some cases looked at it as trying to steal their data.
That's where logs come in handily. All you have to do is select the logging method (personally I prefer NLog, but there are many more libraries out there), and add the log lines.
After practicing logging for a long time I reached the conclusion that a some guidelines are needed. Mainly because no one really needs all the logs all of the time, normally you'll need only the errors, sometimes the warnings too. The lower levels are only used when trying to figure out what causes a certain behaviour.
So here are the guidelines (based on NLog):
1. Fatal - errors that forces the application to shutdown. I'm using it in the global trap, the global threaded trap, and when the application's initialization phase didn't complete successfully.
2. Error - anything that failed to execute correctly without an expected explanation. By expected explanation I mean invalid input data, known erroneous response from service and etc.
3. Warn - anything that failed to execute correctly with an expected explanation.
4. Info - any call to a public method, with the relevant parameters (i.e. username, but not the user's password).
5. Debug - Before entering any "interesting" method, and at the end of each method. By interesting I mean anything that might look like a potential problem to the programmer. For the debug pass EVERYTHING, in some cases even the passwords. When you're forced to use the debug, you have a real bug lurking out there and you must catch it ASAP.
Thursday, August 15, 2013
Interfaces - when to use, and what's an abuse
- Interface is a contract.
- Interface is shared between more than one object.
- Interface promises that a certain method / property will be there.
- Interface does not prevent another interface.
Thursday, May 10, 2012
Silverlight Client Logging
Building a log class is a simple matter:
public static class Logger
{
private static List
private static bool _isActive = false;
public static bool IsActive
{
get
{
return _isActive;
}
set
{
if (_isActive != value)
{
if (value)
{
_isActive = true;
}
else
{
_isActive = false;
lock (Entries)
{
Entries.Clear();
}
}
}
}
}
public static int MinLogLevel { get; set; }
public static List
{
List
lock (Entries)
{
tmp = Entries;
Entries = new List
}
return tmp;
}
public static void AddEntry(int level, string message)
{
if ((IsActive) && (level >= MinLogLevel))
{
LogEntry entry = new LogEntry(level, message);
lock (Entries)
{
Entries.Add(entry);
}
}
}
}
The first thing to note is "GetLogEntries" clears the list. I did it in order to send the entries to the server only once, even if the server did not receive them. You might need to implement it in a different way.
The second thing is I relay on a helper class LogEntry. I provide my implementation of it, feel free to change it, the logic wouldn't change. Maybe a minor change to the constructor is needed. Or using the Server's LogEntry class and setting the properties inside {}.
public class LogEntry
{
public DateTime Stamp { get; private set; }
public int Level { get; private set; }
public string Message { get; private set; }
public Guid UserID { get; private set; }
public Guid TenantID { get; private set; }
public string Operation { get; private set; }
public LogEntry(int p_Level, string p_Message)
{
Stamp = DateTime.Now;
Level = p_Level;
Message = p_Message;
StringBuilder sb = new StringBuilder();
StackTrace stackTrace = new StackTrace();
StackFrame[] stackFrames = stackTrace.GetFrames();
foreach (StackFrame stackFrame in stackFrames)
{
sb.AppendLine(stackFrame.GetMethod().Name);
}
Operation = sb.ToString();
if (WebContext.Current.WFUser == null)
{
TenantID = Guid.Empty;
UserID = Guid.Empty;
}
else
{
TenantID = WebContext.Current.WFUser.TenantID;
UserID = WebContext.Current.WFUser.UserID;
}
}
public override string ToString()
{
return Stamp.ToString() + " " + Message + " - " + Level.ToString() + ": " + Message;
}
}
Now we'll have to add a background sending utility. This implementation is the most naive one, but it works great, so we didn't bother to change it.
private bool IsActive = true;
private void SendLog()
{
while (IsActive)
{
int CountDown = 300;
while (IsActive && CountDown>0)
{
System.Threading.Thread.Sleep(1000);
CountDown--;
}
SendToServer(Logger.GetLogEntries());
}
}
public App()
{
InitializeComponent();
Application.Current.Exit += Application_Exit;
System.Threading.Thread th = new System.Threading.Thread(SendLog);
th.Start();
}
private void Application_Exit(object sender, EventArgs e)
{
IsActive = false;
System.Threading.Thread.Sleep(5000);
}
This code goes to the application class. SendToServer is supposed to be a server side function (using WCF or RIA or whatever is at hand).
That's all, now add the debug lines as Logger.AddEntry(debugLevel, message) whenever you feel like it. Just remember to set the logging level and to activate/deactivate when you need it.