Saturday, April 16, 2016

How to become a Software Engineer (part 1)

It was the end of June 2014 when I accomplished the improbable. I'd become a Software Engineer at Microsoft. Me. The guy who had learned C# on the fly just slightly over 7 years earlier and VB.Net 6 months before that having never programmed on the Microsoft stack before. Today, after answering a question on Quora, I'm going to finally begin a short series of posts that Phil Hagerman requested of me when my career trajectory pitched moonward, starting with a brief history. I'll get more into the details of the hows and whats of what brought me this success in later posts.

The Early Years 

I started programming as a kid on Applesoft BASIC followed by Sinclair BASIC on a Timex/Sinclair 1000. That tiny, 4K beauty had neither a sound chip nor a traditional keyboard. So, it was the perfect, first, hacker platform. My dad and I did the easy thing, first, of upgrading to the massive 16K memory pack. We then cracked 'er open and soldered a ribbon cable between the motherboard and a real keyboard that we built from a kit. Finally, I pounded out and modified every BASIC program I could find, whether originally written for Apples or the TS1000. That culminated with the ultimate hackery that was playing the Star Spangled Banner on a device with no explicit support for sound by detuning the TV such that the video was distorted and, then, poking at video memory, causing said distortion to create consistent pitches. And, yet, I did NOT become a software developer at this point.

Programming? How mundane!

No, I figured I knew all I could about writing software at the ripe old age of 18 and decided that the more interesting subject was understanding the silicon that produced these marvelous things. I didn't get as far in my Electrical and Computer Engineering studies as I'd have liked when, due to financial pressures, I ended up dropping out of college. So, strike two on being a huge success in the software industry.

Gotta Pay the Bills 

I spent the next 20 years working in IT in various ways, from field tech fixing printers for lawyers offices to network admin work. When the company I was making my career with went under, I found myself relocating to central Florida where I discovered the headquarters for one of my favorite pieces of software. The software was Backup Exec and the company was Seagate Software.
Now, in the back of my mind I thought to myself that I could somehow get a job with them and change my career to software development. Mind you, I hadn't written much code in the intervening 15 years. It took me 3 years and a merger with Veritas Software before I could join the company. No programming during that time, either. Then it took another 6 years and completing my AA with a couple of programming courses (C++ and JAVA) under my belt before I tripped over an internal project, Phil Hagerman's brainchild, which I could contribute to. It's not that, before that, I couldn't have been contributing to some open source project (PRO TIP for building your resume) but my self-confidence was low. That internal project, though, was the tipping point I'd been looking for which proved to some folks that I should be doing software development. I was 39 and I was a software developer...barely.

I'm Finally a Developer!

I look back to that time and realize that one knack, that I could arrange my thoughts around a task that I needed the computer to do, allowed me to write really lousy code that got the work done. I had none of the discipline I have today. Like the meme says, "I rarely test[ed] my code, but when I [did], I test[ed] in Production." I had no idea about TDD, design patterns or SOLID principles, or the software development lifecycle (SDLC), or even C#, the language they used, but I was a software developer for a data analytics team in the Symantec tech support organization! (Symantec had acquired Veritas Software by then.) I am not one to go at something half-way, though, so I quickly learned about all those things. After 4 years working on that team, I was ready to stretch my wings and have a bigger impact. Not finding the right opportunity there, I started doing C# and SQL contract work and got a lot of exposure to various SDLCs, architectures, and problem spaces. What I discovered is that I could work on anything as long as I kept an open conversation with my peers and the stakeholders, things I learned well before I became a software developer.

Leaping Moonward

Then the improbable happened. I joined a successful startup that soon thereafter was acquired by Google. And, to my surprise, they kept me on! I was a Googler. Ok, I wasn't a Software Development Engineer but I was programming in Python, another language I'd never seen before, at Google. The interview process introduced me to more new concepts, algorithms and an explicit understanding of the data structures underlying all the work I'd done previously. To remain a Googler, as they were closing my office, I'd have to learn these things to be effective at interviewing so I spent the next year finding all the online courses I could take on the subjects.

Would You Like a Milkshake With That?

Getting Google on your resume brings all the recruiters to the yard. Looking at all the places where I could work for Google, New York, Pittsburgh, Chicago, and Mountain View were on the short list. I wanted to avoid the extreme cold and live somewhere cost effective. When Microsoft called for the 4th time, I decided I needed to practice interviewing and they invited me out to Redmond to interview for a role as a Software Engineer. Redmond in May is beautiful and, to my surprise, they gave me an offer a few days later and moved me, my family, and our menagerie of pets clear across the country a couple of months later. I was a Software Engineer at 46.
I have had successful interviews with Amazon, Microsoft, Google, NASA, and have had continuing offers for interviews at Google, Amazon, LinkedIn, Facebook, Starbucks, Wizards of the Coast (for my fellow geeks out there), and more start-ups than I can count. I am now officially a Software Engineer at 48 and I continue to learn something new every day. I also continue taking online courses to fill in the gaps in my knowledge a CS degree would have covered. I am enjoying this new career trajectory and see myself progressing in this industry for the next 20 years.

Shia LaBeouf Would Be Proud

So, you want to know how I became a Software Engineer? There's a part of me that wants to say it was luck. However, my wife reminds me that it was a lot of hard work, understanding which risks to take, and surrounding myself with supportive people to allow me to take those risks. I'll cover more on those topics in future posts. However, if you are a developer who has always wondered if you could ever work in the big leagues of software, not only do I know it's possible but you can grow well past that. Most extraordinarily as careers go, you can even do it with no formal education. There will be challenges along the way, some internal and some external. Isn't that always the case, though? Don't make excuses for yourself. Just do it!

Thursday, January 28, 2016

StackTrace Tool

Wow! Over a year since I last posted. I really need to find the overlap of time and motivation to bring some value to the few who stop by here. Here's something. I created this and hope you might find it useful. Go check it out on my GitHub repo.

StackTraceTool

Collects tools useful for working with Windows stack trace.

Inspiration & Initial Release

I often have to debug process failures where something unexpected happens. Even when the unexpected is handled through logging, the logged stack trace gets mangled a bit due to the newline character getting turned into escaped text, eg. '\r\n'. So, I'd often stare at this in the tiny message area in Event Viewer or the enterprise's logging mechanism and end up copy/pasting into my text editor of choice and replacing all the '\r\n's manually with actual newlines. My motto has always been, "Don't do any repetitive work if you can automate it."

It'd be easy enough to create a Python script, a console app, or a simple WinForms application. However, I've never built a Universal Windows Platform app and this was the perfect impetus to spin one up. Currently, the prompt is the only feature. However, I welcome input on ways this tool may be improved.

Monday, January 26, 2015

Redundancy in Learning

Did you know that you can learn how to learn? Sounds redundant to me, too, but taking the time to understand the learning process can actually increase your ability to learn. That is the premise of a course on Coursera titled "Learning How to Learn: Powerful mental tools to help you master tough subjects" taught by Barbara Oakley, Ph.D., and Terrence Sejnowski, Ph.D.. In this post, I will share a number of techniques that the course presents which have enhanced my learning aims. These techniques were either quite novel to me or reinforced habits I knew I needed to embrace but had never implemented.

First a quick note about Coursera

If you haven't utilized this free resource for learning then you're really missing out. Coursera is a MOOC, or Massively Open Online Course, site. That means you and hundreds, thousands, or more students simultaneously participate in the same set of online coursework. At Coursera, this coursework may be self-paced, with more of it becoming such all the time, giving you flexibility in when you participate. However, many of the courses are time-scoped. This comes with its own set of benefits, including that you know that your classmates are working on the same material at the same time as you. It also might be useful in preventing procrastination knowing that there is a deadline for the assignments.

Speaking of assignments, Coursera's coursework typically includes video lectures with interactive mini-quizzes embedded within to make sure you didn't doze off during the video. There are often weekly quizzes, which cover the lectures, along with assignments that help exercise what you've learned. Whether via book sites, references, or an actual textbook, there is often also weekly reading assignments to augment the lectures. This is a top-notch education Coursera offers so go avail yourself of their broad offerings.

Now, back to the course

In "Learning How to Learn," the running thread throughout the lectures was the topic of how our brain works and, understanding that, how to leverage what we know about that process. Sounds like it might be dry material but these Ph.D.s are no dummies; they used their techniques in the production of the material they covered. (I would love more instructors to do so.)

For example, did you know that you have an inner zombie? I had never thought about it that way but this was one clever bit of imagery which was used in the course to remind us that we have habits and it's up to you to corral your zombies for good or ill. One such zombie for me was the procrastination zombie. In approaching a new task, it is easier to feel overwhelmed by the task and find a more pleasant distraction. You might think that the answer is willpower but the instructors were clear that willpower is in short supply and best used sparingly. Rather, it's time to create better zombies. In the case of procrastination, understanding your cues and addressing them are the best approach. For me, that cue is feeling overwhelmed by a project. So, rather than focusing on the project, I now focus on the process. The project will resolve itself as long as I have good processes.

Another key learning from the course is the understanding of why you shouldn't cram for tests, whether they are academic or life tests. The imagery used was that of a wall of brick-and-mortar. You may lay a set of bricks with mortar only to a certain load. Beyond that, if you don't let the mortar set first, the bricks will collapse upon themselves. Similarly, it is important to let the new knowledge establish itself on your neural pathways as retrievable chunks before you build on top of that. In other words, study something, step away from it for a time to let it sink in, and then return to it to reinforce what you learned.

Speaking of neural pathways, I'm going to switch to a topic every developer has experienced. You've been pounding your head against a problem for an hour and can't seem to get that breakthrough moment culminating in a bug fix or elegant code. That's known as the Einstellung effect and very common in those of us who have to do a lot of structured thinking,  If you're experienced, you might know about Rubber Duck Debugging. If not, though, even the best of us have experienced a time when, frustrated with the problem at hand, you step away from your desk to share you misery with a peer and suddenly have that "Aha!" moment mid-explanation or, maybe, even before you got to their desk.

What's that have to do with neural pathways? Well, the metaphor the course used to explain these focused versus diffused thinking modes was that your brain is like a pinball machine and when you're focused it's as if there were so many bumpers active that the ball simply can't get to the bottom where the answer lies. Rather, it isn't until a number of those bumpers are dropped out of the field, the diffuse mode, that the ball can bounce around to light upon the bumper which has the right correlation to the solution. So, the next time you're stuck, back off the problem. Tell your rubber ducky what you're trying to do or, better yet, go for a walk. You know you could use the exercise and fresh air.

But back to the procrastination zombie once more before I wrap up because if there is one "killer app" I got out of this course, it's the easiest way to deal with him. As I mentioned, it had to do with focusing on process. As developers, I'd be surprised if you haven't heard of the Pomodoro Technique. However, I bet you either haven't tried it or, like me, gave it a half-hearted attempt and then decided it was ineffective. I'm here to tell you that you need to commit seriously to putting it to use. You don't need to buy the book or even the cute, tomato-shaped, kitchen timer. What you need to do is make a list of ToDo items then, focusing on the process rather than the product, start working on them in 25 minute sprints. Most importantly, make the blasted list!

Look, let's get this perfectly clear. If you aren't making a list right now then you might as well stop reading. Frankly, if you've read this far and aren't making a list then why did you read this far? We all have limited short-term memories. Some of you brilliant readers have far larger ones than I have but they're still limited. So, why are you wasting them on remembering the things you need to get done. Get those things on a list and then forget about them so you can use that space for actual thinking. Have you done that? Good. Go onto the next paragraph.

Get a timer. Use it. After each 25 minute sprint, and this is the hardest part, take a break. Believe it or not, you will be more productive if you do that one little trick. You're a creative person else you wouldn't be programming. Your mind likes to wander. So, let it! Just do so for short bursts. Oh, and if it should wander during the sprint, jot down that stray thought in the list to revisit so you can stay focused until the break.

Now, to wrap that up, let me give you a couple of tools I've found to be perfect for making this work. First, you need a list tool. There are many out there but, since you also need a timer, why not pick one tool that has both. While we're at it, let's pick one that works on any device you may carry with you. My solution is Trello with Trellodoro. You'll need an account with Trello but they're free. Then use that account in Trellodoro to be your timer. It will also let you add completed Pomodoro's (the cute name for a sprint) to your tasks. That part is really optional but if you do it then, after a while, you'll get the added benefit of being able to estimate how much effort a task requires.

You can see that what I learned most from this class was that learning takes time and is not something to do all in one sitting. I knew this. We all do. However, this course gave me an understanding of why this is true. These are just a sample of all "Learning How to Learn" teaches. You'll get test-preparation/taking tips, more detail on how memories are formed, interviews with prominent learners and instructors, and more. As with many classes I've taken, I'm sure I could gain something new from repeated sessions with this course. Besides, as they say in the course, spaced repetition is key to learning. Go check it out. Perhaps I'll see you there one day.

Hiatus Ended

Dear Stalwart Reader,

I, your irregular blogger, am finally returning to writing. It isn't that I haven't had anything to write about, though I'll lie to myself saying as much. It isn't even laziness, which I have my fair share of, which kept me from entertaining and educating you. It's been a whirlwind of change that has left me very preoccupied and wondering which of the many topics to write about.

No more! I have decided to write about every topic that is even tangentially related to software development. I will still try to bring you information unique to the web but I have come to realize that it may not be the details that are unique but my presentation of them. I hope that looking through my unique viewpoint, you may gain something you hadn't before and maybe even something elusive to you before transforms to enlightenment.

If not, I hope I at least entertain you from time to time.

Cheers!
Jack

Sunday, July 28, 2013

Java Memory Troubles? Tweak the GC

tl;dr version

Memory issues? Try -XX:+UseSerialGC -Xss8m in your Run Configuration or as command-line parameters.

The Problem

I'm guessing that, like myself, you have found yourself struggling with the following, two memory errors. I will share what I found as solutions to them.

java.lang.OutOfMemoryError: Java heap space

So, you've taken great pains to use only primitive data types, switching from using an ArrayList<ArrayList<Integer>> for your data type to  Integer[][] even though it's not nearly as easy to work with. You discovered, though, that this just delayed running out of heap space. I tried a solution stated in many other posts on the web and that did seem to help but I can tell you that it didn't fix the problem. Rather, it allowed me to get to the next error.

java.lang.StackOverflowError

I encountered this error for the first time when working on a project with a lot of recursion. This was likely due to what data I was inadvertently putting onto the stack during recursion. However, I recently worked on another project where I couldn't optimize any further the data pushed onto the stack during recursion. Moral of the story is that you should take the time to profile your code and optimize the data which may end up on the stack during recursion. However, after doing your due diligence, you still may need help, though not as much as you might think.

A Likely Solution

After a lot of research, I found the following two StackOverflow.com posts which helped tremendously. First, the solution and then the links. Add the following to your command line when executing your class or add it to your Run Configuration in Eclipse:
-XX:+UseSerialGC -Xss8m


-Xss8m

This first switch is the one that intelligent developers worry will be overused. The default for the stack is 512KB. Suggestions have been as high as 1024m for the number component of this parameter. That's 1GB or RAM! If you need that much RAM for your stack then you might be doing it wrong. Something to note is that I had used this on the highly recursive project I mentioned first with 16m as my value. That got things working. Then I went back and got rid of a bunch of Strings in my code which I'd been using for debugging and I no longer needed this flag at all. Moral of the story, don't use it unless you have to so that you are forced to optimize your code.

-XX:+UseSerialGC

It turns out that the java.lang.OutOfMemoryError: Java heap space error isn't always what it appears to be. Sure, I was running out of heap space but increasing heap space just allowed bad behavior to continue. A flag which is commonly recommended to deal with this error is -Xmx1024m. Again, that's 1GB of RAM. Now, the heap is where your application runs and by default it is given 64MB to work with. So, you could have a valid argument that one day you'll create an application which needs more heap memory. Just not today. It turns out, and this is keyyou just might not need more memory at all!
See, the problem is that Java's garbage collection, while very useful in letting us not worry about destructors and generally functional, can be lazy. So, this is a hint to the GC on how to run. By using this flag, I made it completely unnecessary to use the -Xmx flag. Further, this flag meant that I didn't have to have the -Xss flag set very high, either. Sure, I'm not writing a 4KB demo but I'm pretty happy to have everything working in less than 9MB.

Final Words

I hope you find these flags useful. The key thought, though, is that before you put any of the flags on this page to use, inspect your code with careful attention to the data types used. You may just find that you can slim down a bit and use far less memory in doing so. Remember, one day you might be writing code for a mobile or embedded device smaller than a bracelet, i.e. FitBit, and memory costs money and takes up space.

Tuesday, July 9, 2013

Strategy Pattern using Onion Architecture Templified

The following article was drafted 2013/07/09 shortly after a presentation I had made at the Orlando .NET Users Group. Unfortunately, I neglected to hit the Post button and left it to collect dust without being published until now. Apologies for anyone who was looking forward to this article.

Download demo code

[On 2013/07/09] I presented the strategy pattern at the Orlando .NET Users Group monthly meeting. The gist of it is that if you find yourself starting to write a switch statement or a cascading series of if/else statements, you need to stop for a moment and ask this question: "Will I be having to revisit this someday? If so, how often?" Your answer will dictate if you should proceed merrily along or implement the strategy pattern.

I had the opportunity to ask myself this very question recently when I was required to write a set of web scrapers to get price and availability information off of various retailers' product pages. Some retailers implement the schema.org standard for encoding this product data and others use a proprietary encoding. When my process gets the work item, it includes the URL and the encoding type so I started writing the if/else statement to handle this. Then I re-read the spec and noticed that other schemas were coming down the pike so I started writing a switch statement to handle the ones listed. It was at that moment when I realized that I was introducing a maintenance headache and knew the strategy pattern was the solution.

So, why did I decide that? After all, the strategy pattern doesn't prevent the need for maintenance. What it does very well, though, is moves the maintenance out of the business or (shudder) UI logic, the logic which isn't likely to change often, and promotes it to its own set of classes which actually do the work. The business logic simply asks a context object for whichever class will fulfill the need of the piece of work being handled.

Let me demonstrate using a trivial example for simplicity. I have a web page for doing product searches. On that page, I allow the user to decide if they want to use Bing or Google. When they hit the Search button, the PerformSearch action is called.

private readonly IShoppingSearchAPI _shoppingSearch;

[HttpPost]
public ActionResult PerformSearch(QueryModel queryModel)
{
    _shoppingSearch.Context = queryModel.SearchEngine;
    var searchResults = _shoppingSearch.Search(queryModel.Query, 10);
    var content = "";
    content += "";

    foreach (var product in searchResults)
    {
        var inventoryData = product.InventoriesData != null ? product.InventoriesData.First() : new InventoryData();
        var buyUrl = product.Link;
        content += string.Format("", 
            product.Brand, product.Title, product.Description, inventoryData.Price, inventoryData.Shipping, inventoryData.Tax, buyUrl);
    }

    content += "...creates a table...";

    return Content(content, "text/html");
}

Key takeaways here:
  • The code here knows nothing about how many search engines there are to choose from.
  • The code doesn't make any decisions about the search engines.
  • The code does not need to be maintained when a new search engine is added.
Now, I want to make it clear that this is a contrived example. Only two choices exist and I doubt you'd be adding new search engines to a web page for the user to pick at runtime. However, this clearly demonstrates that such runtime choices needn't be handled in the UI or business logic. Rather, it can be pushed to the outside ring of the Onion Architecture so that maintenance can be managed much more easily.

So, if the code isn't in the UI or business "layers" (Core in my unlayered architecture, name TBD but currently still Onion Architecture Templified) then where does it live? It lives where all code expecting to change often lives, in Infrastructure with an interface in Core. Here's the interface:

public interface IShoppingSearchAPI
{
    SearchEngine Context { get; set; }
    Image Logo { get; set; }
    IEnumerable<Product> Search(string query, int maxResults);
}

And here is an implementation for Google:

public class GoogleShoppingSearchAPI : IShoppingSearchAPI
{
    public SearchEngine Context { get; set; }

    private Image _logo;
    public Image Logo
    {
        get { return _logo ?? (_logo = Image.FromFile(@"%HOMEDRIVE%%HOMEPATH%\Documents\Visual Studio 2012\Projects\CodeCampDemo\Infrastructure\Services\Google\logo4w.png")); } 
        set { _logo = value; }
    }

    public IEnumerable<Product> Search(string query, int maxResults)
    {
        var products = new List<Product>();

        var service = new ShoppingService { Key = "AIzaSyBcWCofOByUzp-EekjUCMxa30h-D9U95eE" };
        var request = service.Products.List("public");
        request.Country = "us";
        request.Q = query;

        var startIndex = 1;
        while (products.Count < maxResults)
        {
            request.StartIndex = startIndex;
            var response = request.Fetch();
            if (response.CurrentItemCount == 0)
            {
                break;
            }

            foreach (var item in response.Items)
            {
                var images = item.ProductValue.Images;
                var image = string.Empty;
                if (images != null)
                {
                    image = images[0].Link;
                }
                var product = new Product
                {
                    Id = item.Id,
                    Brand = item.ProductValue.Brand,
                    Author = item.ProductValue.Author.Name,
                    Title = item.ProductValue.Title,
                    Description = item.ProductValue.Description,
                    Image = image,
                    Link = item.ProductValue.Link,
                    Gtins = item.ProductValue.Gtins
                };

                if (item.ProductValue.Inventories != null)
                {
                    product.InventoriesData = new List<InventoryData>();
                    foreach (var inventoryData in item.ProductValue.Inventories)
                    {
                        var inventory = new InventoryData
                        {
                            Price = inventoryData.Price,
                            Tax = inventoryData.Tax,
                            Shipping = inventoryData.Shipping,
                            Availability = inventoryData.Availability
                        };
                        product.InventoriesData.Add(inventory);
                    }
                }

                products.Add(product);

                if (products.Count == maxResults)
                {
                    break;
                }
            }
            startIndex++;
        }

        return products;
    }
}

Finally, here is a typical context implementation, the place where your switch statement lives:

public class SearchEngineContext : IShoppingSearchAPI
{
    public SearchEngine Context { get; set; }

    public Image Logo { 
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }

    public IEnumerable<Product> Search(string query, int maxResults)
    {
        IShoppingSearchAPI searchEngineInstance;

        switch(Context)
        {
            case SearchEngine.Bing:
                searchEngineInstance = new BingShoppingSearchAPI();
                break;
            case SearchEngine.Google:
            default:
                searchEngineInstance = new GoogleShoppingSearchAPI();
                break;
        }

        return searchEngineInstance.Search(query, maxResults);
    }
}

This implementation of the context likely differs from the examples you'll find on the 'net due to the fact that it implements the same interface as the classes it serves. This simplifies execution by making it unnecessary to ask for an instance before executing the action, doing so immediately instead. I use this improvement in the sample code to leverage .NET reflection features so that I don't even have to maintain the switch statement. Be sure to download the sample code to see how that was done.

So, the next time you find yourself creating a lengthy switch statement or a list of cascading if/else statements, ask yourself if you'll ever have to modify that code. If the answer is yes, then consider spending a little time implementing the strategy pattern and, if you want maximum flexibility, take a look at the demo code for how to do so in such a way that you could just add new classes to your infrastructure code and nothing more for new, related functionality to automagically appear.

Note: Technically, the demo also requires an Enum to be updated in Core but it would be a simple matter to use a string rather than an Enum to enable the same functionality.

Monday, April 8, 2013

Windows Service or Console App? Why not both!

In my organization, I have some folks who love Windows Services and others who despise them, preferring a regular Console App that they can schedule to run using the Windows Task Scheduler. (It's no surprise that the opposite dislike exists.) Further, everyone knows that debugging a Windows Service can be painful. So, simplify your life and make everyone happy (a rarity indeed)! Here's how to do it:

YourService.designer.cs:
using System;
using System.ServiceProcess;
using System.Timers;

namespace YourNamespace
{
    public partial class YourService : ServiceBase
    {
        #region Constructors/Destructors

        public YourService()
        {
            InitializeComponent();

            // TODO: Add code here to initialize your service
        }

        #endregion

        #region Public Methods

        // Added to allow for the Console app to start.
        public void StartConsole(string[] args)
        {
            OnStart(args);
        }

        // Added to cleanly stop the Console app.
        public void StopConsole()
        {
            OnStop();
        }

        #endregion

        #region Overrides

        protected override void OnStart(string[] args)
        {
            // TODO: Add code here to start your service.
        }

        protected override void OnStop()
        {
            // TODO: Add code here to perform any tear-down
            // necessary to stop your service.
        }

        #endregion
    }
}

Proper attribution for the meat of the code above needs to be given to Eienar Egilsson. Go check out the many contributions he's made to the art of development.

Program.cs:
using System;
using System.ServiceProcess;

namespace YourNamespace
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        public static void Main(string[] args)
        {
            YourService service;

            if (Environment.UserInteractive)
            {
                Console.WriteLine("Press Escape to stop program");
                service = new YourService();
                service.StartConsole(args);
                while (true)
                {
                    var cki = Console.ReadKey();
                    if (cki.Key == ConsoleKey.Escape) break;
                }
                service.StopConsole();
            }
            else
            {
                service = new YourService();
                var servicesToRun = new ServiceBase[] { service };
                ServiceBase.Run(servicesToRun);
            }
        }
    }
}