Cross-Training for Software Engineers
Cross-training will actually make you a better software engineer. By understanding the unique features and structure of each project, you’ll gain a deeper understanding of the problem space, and you’ll be able to use that understanding to compose the best possible solution for each individual project.
Join the DZone community and get the full member experience.Join For Free
Disclaimer: No Exercise Required
We don’t often make the comparison of software engineering to running, but in my time doing both, I’ve learned that cross-training is just as important for developers as it is for runners. Competitive runners cross-train by targeting exercises that provide different benefits than running: lifting weights increases strength, plyometrics improve agility, and swimming reduces the risk of overuse injury. Swapping out a few runs for cross-training doesn’t detract from their running — in fact, the variety makes them better athletes.
Similarly, the variety of cross-training will actually make you a better software engineer. Working on multiple similar software projects concurrently rather than just one will help you to consider different (and possibly better) ways of implementing your changes. By understanding the unique features and structure of each project, you’ll gain a deeper understanding of the problem space, and you’ll be able to use that understanding to compose the best possible solution for each individual project.
To demonstrate why this is true, let me tell you a story — a story involving two open-source web frameworks (Flask and Django), one standard distributed tracing API (OpenTracing), and one developer (…me).
Developing a Flask-OpenTracing Extension
The beauty of Flask is that it’s an incredibly simple web framework to learn. In fact, it’s not even considered a full framework, but rather a microframework, since it leaves most design choices (databases, authentication, structure, etc.) up to the user. Most of its functionality actually comes from third-party extensions instead of its core, which made it a great first framework to write an extension for.
Python, the language that Flask uses, has convenient decorators that can wrap methods to extend their functionality. For example, if you want to generate italicized HTML, you could create a decorator @italicize that would return any text wrapped with italics tags as follows:
def italicize(text_generator_func): def wrapper(): text = text_generator_func() return "<i>" + text + "</i>" return wrapper @italicize def hello_world(): return "Hello world!"
Using this decorator on hello_world() results in:
The end result was an extension that allowed users to integrate OpenTracing in their Flask app with one import, one tracer initialization, and a decorator on any view function they want traced. Running the following app and navigating to `/traced-path`, for example, produces a trace.
from flask import Flask from flask_opentracing import FlaskTracer app = Flask(__name__) tracer = FlaskTracer(tracer=some_opentracing_tracer) @app.route("/traced-path") @tracer.trace() def traced_endpoint(): return "This endpoint is traced!" @app.route("/untraced-path") def untraced_endpoint(): return "This endpoint is not traced."
Traces produced for a traced client request to `/untraced-path` (top) and `/traced-path` (bottom).
Moving on to Django
Django, on the surface, is incredibly different from Flask. It has built-in features and a lot more complexity — while a Flask application only requires one file with five lines of code to get started, basic Django apps are separated into several files (settings, URLs, views, etc.) and have a built-in MVC structure. Of course, while this does require more overhead to get a minimal application, it also allows for more straightforward scaling of apps than Flask.
However, these differences are largely user-facing, and I found that from a contributor’s perspective, Django and Flask are actually remarkably similar. My first iteration of Django-OpenTracing was, in fact, nearly identical to the Flask extension. Django uses view functions to handle requests and is written in Python, which meant that the @tracer.trace decorators that I had previously developed for Flask, after some minor tweaks, worked in Django as well.
After initializing a tracer in settings.py:
OPENTRACING_TRACER = DjangoTracer(some_opentracing_tracer)
Tracing of requests could be added to views.py as follows:
from django.http import HttpResponse from django.conf import settings tracer = settings.OPENTRACING_TRACER def untraced_view(request): return HttpResponse("This request is not traced :(") @tracer.trace() def traced_view(request): return HttpResponse("This request is traced :)")
So We’re Done, Right?
Well, we could be. But the awesome thing about Django and all of its built-in features is that it has a concrete concept of middleware, which controls the request-response processing of an application. Rather than just using tracing decorators, I additionally created the following OpenTracing middleware class that, when registered in settings.py, traces all requests to the application.
If we already had a working OpenTracing integration for Django using decorators, why add another one using middleware?
class OpenTracingMiddleware(object): ''' Middleware that adds tracing to Django applications. ''' def __init__(self): tracer = settings.OPENTRACING_TRACER def process_view(self, request, view_func, view_args, view_kwargs): # start a span for the request def process_response(self, request, response): # finish the span that was created for this request return response
The answer is that more options are (almost) never a bad thing. If you want fine-tuned configurability or to decrease overhead by only tracing “problem” view functions, then the tracing decorators are great. However, if you have hundreds of view functions and don’t want to manually add @tracer.traceto every single one, then you can initialize OpenTracingMiddleware in one line and trace everything automatically. Providing developers with two styles of OpenTracing integrations allows them to choose the one that best fits their needs.
So Now We’re Done, Right?
Nope — we’re just getting to the good part.
This experience developing the Django-OpenTracing integration got me thinking more about my integration in Flask. If I could develop two styles of tracing in Django, could I create this additional functionality in Flask?
As it turns out, yes. While Flask doesn’t have middleware in the same sense that Django does, it does have a way to add custom steps to request-response processing. There are two provided decorators,@app.before_request and @app.after_request that you can use to define methods to be executed — you guessed it — before and after each request.
The new release of Flask-OpenTracing now automatically traces every request to your app through one initialization:
tracer = FlaskTracer(tracer=some_ot_tracer, trace_all_requests=True)
Ok, We’re Done.
So what was the point of this story?
Well, I developed OpenTracing support for both Flask and Django. But I also learned a lesson — that you can learn a lot about a framework by looking at other frameworks and comparing the differences between them. Developing packages for multiple frameworks not only made it easier to do subsequent integrations but also directly impacted and improved each other’s functionality.
There are a lot of benefits to cross-training as a software engineer. By working on multiple related yet distinct projects at the same time, you’ll gain experience, efficiency, and a deeper level of understanding of what you’re working on. Perhaps most importantly, though, you’ll achieve a wider impact. Take open source projects like Flask and Django for example: if you use cross-training to make more, higher quality contributions, it will improve the development experience for a whole community of users. And at its core, maximizing positive impact is what becoming a better software engineer is all about.
Published at DZone with permission of Kathy Camenzind. See the original article here.
Opinions expressed by DZone contributors are their own.