Chatbot 04: Code organization using separate files and functions

Organizing code is essential to future work on code. It allows for re-use, which increases development velocity. Done correctly it provides ease of initial use, which in turn decreases the amount of time someone else needs to understand your code. There are a plethora of approaches for code organization – and more then one of them are right. How I think about code is not how someone else thinks about code. But, with a decent amount of organization – we can both understand the code and build upon each others successes. It is one thing to develop code in a vacuum. The next level is building in tandem with a team.

Let’s dive right into it. https://github.com/Waryway/chatbot/tree/primary/chatbot-lesson-04-organize is the fourth directory for the chatbot. If you remember from the 3rd lesson – our main.go file was getting a bit lengthy. Nothing terribly egregious, but just enough where we can use it for an example on reorganizing code. Now, bear with me – there is a lot of code listed below – feel free to scroll past it.

Organized Code Files

// main.go
// main function
func main() {
	var history handlers.History
	err := history.Initialize("history.txt").LoadHistory()

	// check for the error.
	if err != nil { // a 'nil' error means no error occurred.
		fmt.Println(err) // print the error
		return           // stop processing the context.
	}

	var echo handlers.EchoBot
	echo.Initialize(history).GreetUser().ChatLoop()
}

// history.go

type History struct {
	Location string
	Current  []string
	file     *os.File
}

func (h *History) Initialize(location string) *History {
	h.Location = location
	return h
}

func (h *History) LoadHistory() error {

	err := h.open()
	if err != nil {
		return err
	}

	defer func() { _ = h.close() }()
	// Slice for storing the History of the world.
	scanner := bufio.NewScanner(h.file) // use the buffer input output package to start a file scanner
	for scanner.Scan() {                // loop through the file scanner
		h.addRecord(scanner.Text() + "\r\n") // add the scanned line to the lines slice.
	}

	return nil
}

func (h *History) Record(record string, isPrintable bool) {
	if isPrintable {
		fmt.Print(record + "\r\n")
	}
	h.addRecord(record + "\r\n")
	h.writeRecord(record + "\r\n")
}

func (h *History) addRecord(record string) {
	h.Current = append(h.Current, record)
}

func (h *History) writeRecord(record string) {
	_ = h.open()
	defer func() { _ = h.close() }()
	_, _ = h.file.WriteString(record)
}

func (h History) SaveAll() {
	for _, record := range h.Current {
		h.writeRecord(record)
	}
}

/**
 * This will perform the three error producing steps to erase the History file.
 * 1. Close the writer
 * 2. Truncate the History file and the local History
 * 3. Re-open the file
 */
func (h *History) EraseHistory() error {
	err := h.close()
	if err == nil {
		err = os.Truncate(h.Location, 0)
	}
	if err == nil {
		h.Current = []string{}
		err = h.open()
	}

	return err
}

func (h History) close() error {
	return h.file.Close()
}

func (h *History) open() error {
	f, err := os.OpenFile(h.Location, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	h.file = f
	// check for the error.
	if err != nil { // a 'nil' error means no error occurred.
		fmt.Println(err) // print the error
		return err       // stop processing the context.
	}

	return nil
}

func (h History) Length() int {
	return len(h.Current)
}

func (h History) Print() {
	// iterate (loop) through the history
	for k, v := range h.Current {
		// print out the Key and the Value
		fmt.Print(k, v)
	}
}

// echobot.go
type EchoBot struct {
	greeting   string
	userPrompt string
	history    History
}

func (e EchoBot) Initialize(history History) EchoBot {
	e.greeting = "Hello, World! \n I am Echo! Please tell me something to say by typing it in, and pressing enter!"
	e.userPrompt = ">"
	e.history = history
	return e
}

func (e EchoBot) GreetUser() EchoBot {
	e.history.Record(e.greeting, true)
	return e
}

func (e EchoBot) ChatLoop() EchoBot {
	// string for storing input
	var input string

	// Stop when we see "bye"
	for input != "bye" {
		e.prompt()
		scanner := bufio.NewScanner(os.Stdin)
		for scanner.Scan() {
			input = scanner.Text()
			break
		}

		// Add the new input to the history
		e.history.Record(e.userPrompt+input, false)
		e.history.Record("Echo: "+input, true)
		// Check if the input was the string 'history'
		if input == "history" {
			// We won't add the history to the history, instead we can add how many lines of history exist.
			e.history.Record("<Truncated> "+strconv.Itoa(e.history.Length())+" Lines of history repetition.", false)
			e.history.Print()
		}
	}
	return e
}

func (e EchoBot) prompt() {
	fmt.Print(e.userPrompt)
}

Lesson 3 Code Reference

// main function
func main() {
	f, err := os.OpenFile("history.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)

	// check for the error.
	if err != nil { // a 'nil' error means no error occurred.
		fmt.Println(err) // print the error
		return           // stop processing the context.
	}

	defer func() {
		_ = f.Close()
	}()

	// string for storing input
	var input string

	// Slice for storing the history of the world.
	var history []string
	scanner := bufio.NewScanner(f) // use the buffer input output package to start a file scanner
	for scanner.Scan() {           // loop through the file scanner
		history = append(history, scanner.Text()) // add the scanned line to the lines slice.
	}

	fmt.Println("Hello, World!")
	fmt.Println("I am Echo! Please tell me something to say by typing it in, and pressing enter!")
	_, _ = f.WriteString("Hello, World! \n")
	_, _ = f.WriteString("I am Echo! Please tell me something to say by typing it in, and pressing enter! \n")
	// Stop when we see "bye"
	for input != "bye" {

		fmt.Print("You: ")
		scanner := bufio.NewScanner(os.Stdin)
		for scanner.Scan() {
			input = scanner.Text()
			break
		}
		_, _ = f.WriteString("You: " + input + "\n")
		// Add the new input to the history
		history = append(history, input)
		fmt.Print("Echo: " + input + "\n")
		_, _ = f.WriteString("Echo: " + input + "\n")
		// Check if the input was the string 'history'
		if input == "history" {
			// We won't add the history to the history, instead we can add how many lines of history exist.
			_, _ = f.WriteString("<Truncated> " + strconv.Itoa(len(history)) + " Lines of history repetition.")
			// iterate (loop) through the history
			for k, v := range history {
				// print out the Key and the Value
				fmt.Println(k, v)
			}
		}
	}
}

Break down of changes

  • Created an independent concept of History
  • Created an independent concept of Echobot
  • Main handles really only handles the initialization, and kickoff of the Echobot

But how did I come to decide those were useful items? Well, looking through the original main.go file, I noticed we were doing a lot of ‘history’ type operations. In noticing this, I considered the concept that, maybe in the future – something else might need to write history. I also considered that idea that history might need to be written to another location. Separating history, and setting it into its own area proved a bit more work then just isolating the same calls from main.


Step 1. Creating a history object

I work a bit through trial and error. I’ve learned in the past, that I like talking to objects. It keeps an object in its own state, it allows me to mock out an object on a whim for testing. It even gives me operations on the object in a simple to reference location. With this in mind, I created the History struct. I then proceeded to add an initialization step. This just makes certain the object knows where it will persist data. It keeps things simple.

Step 2: Adding functionality

Because an object was used – I am able to call the object’s functions and keep the objects state readily available. This will allow me to potentially have different histories, but at the same time, largely avoid having to manage which is what when running methods for the objects.

To figure out the functions for the object, I reference what the main.go originally did. Then, I proceed to create distinct functions for file i/o or history operations. This makes it much easier to find a problem in talking to files, or in actually ‘logical’ operations – such as Print, or Length.

Step 3: Repeating the process somewhere else.

After going through the history object – I quickly came to realize that the main code was still a bit lengthy. (Apologies, I didn’t record the code in this state!). Evaluating what was left in main, and thinking about the goal of this project. I decided the only proper thing to do would be to make an actual echobot struct for handling echo details.

Echobot had some really clear purpose. It needed to listen, it needed to loop, and it absolutely needed to talk to the previously created History object. This quickly led to a lot of dependence on the history object, but, at the same time allowed the concept of a ‘forever listening’ echobot to be present and have its own historical record. This greatly changed the state of the program, without actually really changing anything for the end user.

Summary

Sometimes – you can plan ahead for how you are going to organize pieces of the code base. This will allow you to write tests for you code with ease alongside developing the code, or, in the case of test driven development. It could allow you to design your tests ahead of your code. In this example, we didn’t create any tests. However – with the reorganization of the code – the next step is truly creating tests to make sure things are working as expected. Look out for the next step – Unit Testing your Chatbot

Advertisement

Uh mazing: 01 An introduction to 3d game development

For this post, we’ll cover a what goes into building a 3d game. Fun, right? Maybe not super exciting but there are some important concepts we need to cover. Without further ado, getting started designing a video game for newbs.

If you’re interested in building video games, you’ve probably played a video game or two in your time. I know I’ve wasted way to many hours… When you’re playing any 3d game, you’re perspective of the world is usually either 1st person (most shooters) or 3rd person (most RPG’s) Or, you might observing from above (most an RTS). It turns out, these don’t make much difference once you get further into building the world. What you probably don’t think about, is the fact that you, or your avatar, are not moving. That’s right – in a 3d game: You don’t move – the rest of the world does.

In a 3d game: You don’t move, the rest of the world does

Graphics Rendering, a summarized explanation

Now, let me explain how graphics work, in a nutshell.

  1. You have a flat 2d screen.
  2. The screen has pixels!
  3. Just like a painter, you are tricking the human eye into seeing 3d
  4. Graphics rendering starts with a single pixel, moves to a line, then a triangle.
  5. The entire world is made of triangles
  6. Every detail added on top of that, is just enhancing groups of triangles.

Pretty simple right? Drawing triangles? I tend to think so. The real magic with the triangles – comes with positioning, and movement rules. Positioning is when things start to get a bit more complicated.

I took a video game design course in college. Some of the best take-aways from that course were which libraries to use, and, how to use the positioning. The easiest way I can explain positioning, or coordinates, is to start with a square. Now, draw two lines, one from top to bottom, the other from left to right. You now have two axis. Usually, the top to bottom one is the Y axis, and the left to right one is the X axis. So – if you move the Y axis along the X axis (draw the top to bottom bar a little to the right from where it started) You’ll have moved to a new coordinate along the X axis. The way we represent these positions in software are with Vectors. vector(1.0f, 2.0f) would mean a position of 1 on the x and 2 on the y axis.

Rendering

Now, lets go 3d! Remember the square, and drawing the lines across it? Do that with a cube. Yes, you’ve suddenly got a third line going from front to back. This is the z-axis. A vector can represent this as well. vector(1.0f, 2.0f, 1.0f) where the 3rd item is the position on the z-axis.

Now, it’s great to travel on axis – but if we can only look one direction, how do we turn, or look up? This is where rotation comes into play. And here’s where the programming gets tricky – because now, instead of just moving the world around your perspective – you need to rotate the world around your perspective, yet not rotate everything in the world at the same time! Fortunately, this is where other’s work can be helpful. Rendering engines take care of a lot of the complicated math using well known, powerful algorithms. If you’re still curious about using a camera approach, here is an example of a camera, and render method using C# and OpenGL

Textures, Sounds, and Networking

It isn’t enough to simply draw lines, squares, or donuts. For a game to be immersive it will need the donut to look like it got home from the bakery, the line will need to go ‘zap’ as it flashes by, and the kid across town is going to need to know where the square is. These auxiliary pieces to a game are crucial to some of the best games out there. To some extent, these can be overlooked in 3d world development. At the same time, the can’t be missed.

These will be covered a bit later on – there are tutorials out there already. And it is possible to develop the piece in some isolation. Well, textures might belong to the engine. that said, there are lots of opportunities to cover them.

Summary

This introduction covers some of the different high level aspects of the 3d world. Because 3d development is such a large item. Having a plan on what to attack next is useful to avoid scope creep and other difficulties getting anything working. As this guide is built out – it will somewhat follow my own approach to a development plan.

Old Code: Don’t call it legacy, stale, or deprecated.

While working on an Analytics platform, I approached my manager about some of the older pieces of analytics. A section of code known for its complications and quirks – written by people no longer present at the company. I had been tasked with isolating this rather large stack into isolation – and it needed a name. I suggested Legacy, and was surprised by how quickly that idea was turned down. He explained his reason to me – but now I get ahead of myself.

A phrase often heard and acknowledged throughout development is that, “naming is hard”.  He we are a collective lot of somewhat intelligent people, and yet we stumble at naming things. Maybe this is because “Engineers can be weird“. Maybe Shakespeare has been misrepresented by one of his characters on the smell of stink-buds. Maybe naming is hard, just because of what it represents. Which still leaves the question, “Why can’t I call it legacy?”  Without further ado, here are different stages of ‘legacy’ and some ideas for names.

Stable: not likely to change or fail; firmly established. – Oxford Dictionary

Replacement plan

Needed

You’ve identified old code. It’s massive, it’s unwieldy. It’s still your main core code, but it doesn’t change much anymore. I’ve seen code at this point referred to as stale – but try to avoid that. Instead, call it stable – because that’s what it is. Calling something stale has strong negative implications. Now, inside your development group – you’ll know what’s wrong with the code from a technical perspective. And calling it stale implies those ‘wrong’ things are not getting cleaned up. But let’s say that term spills out of engineering. Will this I instill business wide confidence in development engineers? No. It will most likely invalidate developers because they produce stale things. Calling it stable will show that things can be built to last.

Deprecated: (chiefly of a software feature) be usable but regarded as obsolete and best avoided, typically due to having been superseded.  – Oxford Dictionary

Determined

That old code that has been sitting around? “Old Busted”, we’re going to use the “New hotness”. Unfortunately – this is a common point where “Old Busted” is referred to as deprecated. This is where the first portion of the definition of deprecated is used. What is really needed before we can call it deprecated is the actual replacement. So again, keep calling it stable code. This will bolster morale for anyone still working on it – whereas calling it deprecated will cause developers to head for the hills. Calling it legacy at this point also has the same effect on developers. Who wants to care about something going away?  Well, the people keeping track of the money do. You want everyone to care about the old code – until the point it is truly gone.

In progress

“Old Busted” is shrinking. It’s almost getting to a manageable place – there’s still a dev or two working on it – One might think now is the perfect time to call it legacy code. But it isn’t. In fact – don’t call it legacy until it is gone. At this point, there are areas that can truly be called deprecated. The “New Hotness” has in fact replaced the old in a shiny new ways. But it likely hasn’t covered everything at this point. Continue to refrain from using negative connotation description of the stable code.

Completed new work

The new code is dev complete. A lot of it is out in production – there might be a few dangling deploys. The A/B test is still in progress – but the new is clearly taking out the old. Now is a time to party! Call the old code deprecated. Refer to it as legacy – revel in the victory. Then summarily delete the code base and forget it ever existed. It’s what we do. It turns out, pretty much anyone can write a line of code – but removing old lines of code that are no longer relevant. Or even the ability to identify them. That is true skill.

Summary

It isn’t the end of the world if you call something ‘dead’ before it’s dead. Everyone will go along with it. But, do you really want to make good devs suffer for maintaining the bottom line? Or decrease the already small amount of trust business folks have for devs? (Maybe a future article about smart, untreatable, developers?). In the end, the words we use to communicate convey context. Personally – I’ll take a positive context spin over a negative one every day.

 

Dev Profile: Maintenance Master

Introduction

If you’ve ever worked in a service industry, you’ll have noticed that there are many people that just do their job. They go to work, do what they are supposed to, collect their paycheck, and punch out at the end of the day. They fill a role, and they do well at it. They stay out of the spotlight – their ‘off’ time being their fulfilment, and the job being an ends to a means. The same holds true in the Software Development environment.

Software stacks are large these days. Well, at some startups the code base is still growing, but even their, the open source code being referenced is quite large. Now, it’s great when a developer / architect can build and design these larger complicated systems. But as time goes on – new things don’t need to be built out so much as in the past. Sure, things like GDPR can wreak havoc on websites, but for the most part, code bases are pretty stable.

Ideal Scenario

An example of where a Maintenance Master is most suitable: Imagine a stable codebase which doesn’t need to be completely re-written. It needs to be maintained. Minor to Major bugfixes, improvements, business logic changes – these are common maintenance items. This type of work can be well planned out, well tested, and scheduled. Which makes it the ideal scenario for Maintenance Masters.

Advantages

Being able to keep a code base alive and relevant is no small task. Knowing the reason for business logic implementations. Having a history of decisions. Doing all this without checking the Repository history or checking the resolved tickets list is a huge asset. In addition to this, they are great at sticking on a problem until it is eventually resolved. They’ll guard their area of expertise and are predictable about the deadlines they can meet. Sometimes they’ll explore new ideas or concepts – but they are largely happy with the status quo. They are able to churn out a lot of code – but it’s going to be the same code style repeated in every scenario.

To summarize:

  • Consistent
  • Dependable
  • Thorough
  • Content
  • Knowledge Stores
  • Persistent
  • Produce a lot of code

Disadvantages

Being able to stay on a single project or code area for a long amount of time might be great – but it is countered by being unable to see larger solutions or newer approaches that can clean up the code. For example – the DevOps movement has put a huge dent in the abilities of maintainers. Now, their entire process must be revamped. This in turn causes maintainers to become disenchanted with their code or their company – and often will result in their departure for a more stable opportunity.

Not being a part of the huge ‘go getter’ mindset, maintainers don’t typically go way above and beyond the job description. They aren’t typically looking for a promotion and often watch others pass them by. They’ll sometimes get stuck maintaining a failed piece of code without realizing a refactor would be more useful in the long run.

They also sometime lack in their communication skills. They know their job – they expect you know yours, and they don’t anticipate communicating what they are doing outside of what the process makes them communicate.

Managing a Maintainer

There are some obvious do’s and don’t’s when managing or leading a maintainer.

Do’s

  1. Keep their work planned and organized
  2. Give them an area to own
  3. Give them some autonomy
  4. Keep their environment stable
  5. Provide early notice for large changes conceived for their area
  6. Use them to train new people

Don’t’s

  1. Haptic switching of their projects
  2. Expect new ideas
  3. Promote them to architect positions
  4. Use them to interview
  5. Push them outside of their comfort zone.

Summary

Maintenance Masters are an excellent piece of a development team. They will keep the lights on and things stable. They won’t push much of a changing agenda – but they will get things done. If you blame a code base, you’ll find their name dispersed through a file – not as an originator, but as an editor. There is absolutely nothing wrong with being a maintainer – however – if you are one, and you want to move into management or leadership, you’ll need to step out of your comfort zone.

Dev Profile: Emergency Response Hero

Someone just noticed Production fell over. Ops is looking into the situation. Devs are looking into the situation. Dev Managers are hovering – the situation is tense. In terms of the Restaraunt Analogy – Someone sent the wrong dish to a customer, and the kitchen is trying to recover. Enter the Emergency Response Hero (EMH)! With one look at the problem – he asks a few questions – fly’s through recent commits, check configs, and quickly ascertains where the problem is. Without telling anyone – he jumps towards the fix – and lets everyone know, “Hey, I’ve got it. Here’s the problem. Here’s a fix!” The fix goes out and all is well with the world. Until the next emergency. Everyone lauds the hero with accolades! …and the person responsible for the initial issue hangs their head in shame.

In the real world – as opposed to the world inside a development shop – Emergency response heros save lives every day. Natural disasters and accidents are common. We live in a chaotic universe. We need and appreciate our Emergency personnel for good reason. But what about a controlled environment? It’s a great thing to have someone capable to recover quickly from an issue at hand. However, there comes a point where relying on emergency personnel all the time is a problem.

Do you have someone in your team that wear’s a fireman’s hat (or equivalent) to work?
Do you have someone unable to complete and planned work as they are always saving the day?
Do you know a dev that gets more accolades then anyone else – but not for innovation but rather fixing things?
If you answered yes to any of these, you might be working with an EMH.

Working with an EMH can be rewarding for a dev manager. But, at the same time, they can demoralize the crew. Some will respect his accomplishments. Others will be ambivalent – and others, hard workers that don’t get accolades, will be jealous. When people become jealous, they become demoralized. Then productivity falters. But how to avoid this? Personally, I recommend better Automated Testing. A highly visible EMH is a strong indicator that Automation is not doing its job. Try to get ahead of this curve before it is too late. EMH’s get burned out too.