Why I love blogging

by Rob Galanakis on 29/03/2014

I started to write a post about how I missed multithreading and speed going from C# to Python. I ended up realizing the service we built which inspired the post was poorly designed and took far more effort than it should have. The speed and multithreading of C# made it easier to come up with an inferior design.

The service needed to be super fast, but the legacy usage pattern was the problem. It needed to run as a service, but then we realize it’ll be running as a normal process. This is what happens when you focus too much on requirements and architecture instead of delivering value quickly. You create waste.

I wouldn’t have realized all of this if I didn’t sit down to write about it.

No Comments

Configuration in Host the Docs

by Rob Galanakis on 28/03/2014

With Host the Docs, I chose to eschew configuration files and use a sphinx-style “conf.py” approach (I have previously written about how I like this approach). If a conf.py file is found, it is used for configuration, allowing it to override default configuration options. I also allow configuration through environment variables, where the environment variable name is the same as the conf.py variable but with HTD_ prepended.

So for example, in hostthedocs/getconfig.py, I have code like the following:

try:
    import conf
except ImportError:
    conf = None

def get(attr, default):
    result = os.getenv('HTD_' + attr.upper())
    if result is None:
        result = getattr(conf, attr, None)
    if result is None:
        result = default
    return result

welcome = get('welcome', 'Welcome to Host the Docs!')
# Other configuration values

I was initially going to use a json or yaml file, but always find their contracts a bit unclear, especially when paths are involved in the configuration (if the config uses relative paths, what are the paths relative to?). It also involves more code.

I was really happy with how using Python for configuration worked out in Host the Docs.

1 Comment

“What did you learn?”

by Rob Galanakis on 25/03/2014

When something bad happens to someone (firing, demotion, bad review, big failure), it’s natural for managers to ask that person “what did you learn?*

Unfortunately the answer is rarely what a manager wants to hear, and it’s also largely useless.**

Asking the question phrases it as the employee’s problem, while theory and experience both tell us that it’s far more often the management that is at primary fault (work environment, culture, all sorts of common cause variation. It is not at all useful to ask the employee “what did you learn?” unless the goal of the question is a) pure personal curiousity, as when family/friends/coworkers ask, or b) to get the employee’s take on how to improve the system so these things don’t happen.

Are we masters of our own destiny? I find thinking so is a useful way to behave personally, but a naive or dishonest way to lead and manage. If you believe that an employee is the primary driver of their behavior, rather than the system he or she works in, then you’re probably relying on destiny to create a successful company. Good luck! You may also consider playing the lottery.***

Unless you can reliably improve and grow the system and culture, you are relying on luck, timing, and personal attributes to create a successful organization. That’s fine, but very few managers would admit to wanting such a company. An event that disrupts or upsets an employee is a great opportunity to learn, so here are some better questions to ask your employee when it happens. (for clarity, I will use “we” for management and “you” for employee)

Better questions to ask your employee

What can we learn from this? I assume we think highly of our employee (we hired them****, or at least haven’t fired them before this). They probably have the best view of what went right and wrong. And if not the best view, then at least a unique one worth hearing. We have as much to learn as they do. After all, we- the manager, employee, and probably many other connected people- failed.

What do you think caused this? Your employee fell victim to the system. The difficult part is figuring out what parts of the system were the aggressors. Remember, even if this were truly their fault, our organization can’t grow from finding the employee at fault, so it behooves us to find something we can learn from.

Were you surprised? When people take risks, they expect to lose part of the time. The employee’s failure may not be due to a mistake, but a calculated risk that didn’t work out. This fundamentally changes what the “learning” is about. Let’s not presume bad outcomes were due to a lack of understanding or miscalculation on the part of the employee, but rather assume bad outcomes are due to a shortcoming in our management and the system.

Why were we surprised at the outcome? Why didn’t we see this coming? If we did see it coming, why didn’t we act earlier? Maybe your employee can help you learn to see this happening earlier next time, so you can do something about it. Or maybe you were warned about it, but saw what you wanted to see, and your employee can help you realize that.

What would “success” have looked like? Some situations are “unwinnable.” There war is lost, but the employee still believes there is hope. Find out what they were fighting for. You can make sure others that are fighting for the same cause don’t meet the same fate, either because you fix the issue, or do a better job explaining it.

There are many other good questions that should be asked. Big “failures” are great opportunities to learn, so let the discussion flow. You won’t learn and grow as an organization if your default response is to blame the employee. Every time someone gets a bad review, is fired, quits, or royally messes up, we must use that opportunity to improve as an organization.


*: I will state so there’s no confusion: I’m not talking about the words themselves. I’m talking about the idea that you are asking the employee what they learned as the primary way to involve them in the retrospection of “their” significant failure.

**: Usually what the employee probably thinks is you’re an asshole, the culture is broken, and the organization is fucked. So I spend this article focusing on why the question is useless.

***: I don’t worry about offending people here, because no one ever thinks they’re a bad manager. And you certainly aren’t!

****: I use ‘they’ or ‘their’ instead of “he or she” or “s/he” or whatever. I’m not sure what is in vogue today.

10 Comments

Introducing Host The Docs

by Rob Galanakis on 22/03/2014

A month or so ago I created Host the Docs, and it’s been quietly running at work some success, so I figure it’s time to talk about it. The code is on GitHub at https://github.com/rgalanakis/hostthedocs, and here’s a description from the readme:

Host the Docs is a simple way to host static code documentation. Its main use is as a self-hosted server for your organization’s private documentation. Better alternatives are available for open source projects, such as Read the Docs or Github Pages.

Host the Docs was created after a long day of banging my head against the wall trying to get Read the Docs set up with private GitHub repositories, and having helped develop a plugin to get it to work with Perforce previously. What the world needed was a way to easily host documentation from several projects, from any source or language or SCM.

Seriously, let other people generate their own docs, I just want to Host the Docs!

It’s super easy to get set up, and requires no database. You can even just serve everything through Flask, if you don’t want to set up a webserver like Apache or nginx. It’s pretty well tested and configurable and thoroughly documented. We had it running on a new Linux VM in just a few minutes, and use it to host HTML documentation for Python and C# projects (we used Sphinx to generate the Python docs and Doxygen for C#).

I encourage you to try it out if you are interested in painless documentation hosting! I’m also very open to fixes/changes/pull requests, as long as it doesn’t overcomplicate things.

No Comments

Who is using PyPy?

by Rob Galanakis on 11/03/2014

I brought up the idea of using PyPy for an internal service we are building at work, and was asked what actual projects are using PyPy? I had no answer and couldn’t find one. Are there are well known projects using PyPy? Do you know of anyone that is using it to run small servers, even? I find PyPy hugely exciting, and I know it’s current limitations and issues (as well as its benefits), but I’m wondering if anyone’s used it yet and what their experience has been.

7 Comments

Being amazed by software development

by Rob Galanakis on 1/03/2014

I am continually amazed by the state of software development. I am amazed at how broken things seem to be, and I’m amazed at what powerful tools we have to fix things.

A few weeks ago, I struggled to find a documentation hosting solution. We have an internal version of Read the Docs which took far too much effort to get set up (dependency problems, creating a new SCM backend) and administrate, but Read the Docs doesn’t work with private Github Repos. After some hacking, we still couldn’t get it to work. So I looked at hasdocs.com, which promised to host our docs in the cloud, but was totally broken. I looked around at other solutions and was tearing my hair out. I was amazed that no good solutions already existed, and was more amazed that the best solutions are somewhere on the spectrum of broken (no offense here to RtD).

At the end of the day, it occurred to me my use case was incredibly simple: just provide an index page for (already generated) HTML documentation. Let people POST their HTML files and some metadata, and just dynamically generate the index based on what’s available. I was going to run this behind a firewall, so I didn’t need to worry about security. Hell I could even get away with no database, and just read the filesystem.

The next day (a Saturday), while my son and wife napped, Host the Docs was born. It took 1.5 hours to create an initial working version, and then I spent a few hours here and there polishing things up. It was painless to deploy, and I did some more improvements after that. Throughout this experience, a few things struck me:

  • I’m amazed by frameworks like Flask and Bootstrap. You can create a reasonable site in no time that is totally maintainable. At no point was I “hacking” to get something up and running, it was instead a very small first version I was able to iterate on.
  • I’m amazed by Linux. I have to use Windows at work, but feel like I develop faster on my Linux Mint netbook as I do on my Windows 7 workstation. The power at my fingertips is divine. It makes me mad at Windows.
  • I’m amazed how well some software works the same way I’m amazed at how broken some software is. Software is truly evolving; for every Flask, there are confusing and broken web frameworks.
  • I’m amazed how much time having autonomous teams can save. It would have taken a day or more to deploy HTD if I had to go through IT to provision a normal virtual machine (I doubt that frustration is unique in our industry). Instead, by giving teams an AWS budget and not centrally controlling things, HTD was live in minutes.

I’ll post more fully about Host the Docs later. I just wanted to express my satisfaction before it wore off :)


(BTW, I’m aware how similar this sentiment is to Jeff Knupp’s story of building Bull: Python and Flask are ridiculously powerful. Probably not coincidentally it is also a story of using Flask :)

6 Comments

Agile project management versus agile development

by Rob Galanakis on 24/02/2014

I have a saying I like to use when discussing Scrum: Scrum is an Agile project management methodology, not an Agile development methodology. Scrum delivers tools for managing the project (planning, scheduling), but very little for how to develop (design, program, test) it. To do Agile properly, you really need both. This is why eXtreme Programming (XP) and Scrum “fit together” so nicely, with XP telling you how to build your product and Scrum usually taking a higher-level view (and the overlaps are usually the same practices).

The complimentary nature of project management and development methodologies is important to understand and embrace. It is also one reason I don’t believe you can implement Scrum effectively without a programming background. Ultimately this comes down to a very simple thing for me:

If you do not have comprehensive automated tests, you cannot be Agile.(1) I consider this a fundamental truth. Yet rarely do books about Scrum spend more than a few pages talking about how vital this is, rarely is it discussed enough in Scrum Master training courses, rarely do the project managers now running most Agile implementations seem to understand this.

Automated tests are the fundamental building blocks from which all other Agile practices flow. This also informs how I treat Agile as a whole: Agile project management should be free-flowing and not rigid, but Agile development should be rigorously adhered to. That is sure to be inflammatory so let me elaborate.

Rigorous Agile development

  1. If automated tests are fundamental, and Test Driven Development (TDD) is the only way to get comprehensive test coverage, that means TDD is not optional. You don’t need to use it everywhere, but TDD should be the rule, not the exception.
  2. In the same way, you cannot be Agile if one person ends up being a bottleneck, so collective code ownership is required. I think it’s less important whether this is done through pairing or code reviews (probably both), and more that collective code ownership is a rule and any exceptions are seen as a big problem. Automated tests are required to collectively own code, as it’s the only way people can reliably make changes.
  3. Continuous integration is equally important, and depends on automated tests to be reliable and problems to be quickly fixed (it relies on the other two practices). You must always have a high quality build available and the code people are writing should be quickly (once a day or more) and easily getting into that build.

These three practices I consider absolute.(2). Maybe you can add some, but you are not allowed to decide you want to exclude any of those three from your Agile implementation.(3) To do so invalidates the principles on which Agile rests. So it follows that you should not be allowed to have zero experience in them if you’re an Agile leader. You cannot be Agile without them, no matter how briefly Scrum literature covers these topics; I would bet most of the writers of that literature would agree.

Flexible Agile project management

Opposite the rigorous adherence to specific development practices is experimentation with general project management practices. In this area, things are much more about principles (primarily feedback and continuous improvement) and less about practices or processes. Your sprint should be as long as you need to get good feedback, which varies depending on project/team/technology. Your retrospectives should be run so you can continuously improve. Your planning should be so you can get more accurate and insightful. Ten solutions can work well at ten different workplaces. Even more, ten solutions can work well at the same workplace(4). Just make sure you are continually improving, and keep trying new things!

Having your cake and eating it too

This distinction between development and project management is how I navigate the rift between Agile nihilists and Agile purists. The former say with disdain, “Whatever works for you!” The latter chant fervently, “You cannot pick and choose!” It turns out they can both be right, but it’s important to understand how and why. The nihilists end up floating, never realizing the transformative power of Agile because they refuse to adhere to the three vital, but initially taxing, processes. The purists can drive change, but not transform, because they do not create new practices that fully embrace each unique situation.(5) Rather than trying for a happy medium between the poles, I find Agile is best done by being at the extremes simultaneously.


  1. I don’t know how to rebut arguments against this point, other than asking “have you worked on a project that was well-developed with TDD?” If not, I would try it out before you make excuses for a compromised form of Agile without comprehensive automated tests.
  2. More accurately, what they achieve I consider absolute. If you wanted to get rid of any of them, you’d need to replace them with something suitable. For example, if you didn’t want to use TDD, you’d need to demonstrate some other way to reliably build comprehensive automated tests. And actually I have great hopes for some automated alternative to TDD one day…
  3. Uncle Bob recently posted about how software projects need this sort of discipline, perhaps by having a “foreman”. I don’t agree on the solution, but I do agree that we do need to rigorously adhere to certain practices. http://blog.8thlight.com/uncle-bob/2014/02/21/WhereIsTheForeman.html
  4. This is probably attributable to the Hawthorn Effect: http://en.wikipedia.org/wiki/Hawthorne_effect
  5. To be fair, many purists (especially outside of programming) overlook the three vital development practices because they are so keen on implementing the easier Scrum project management tools that require less training and invasive changes.
5 Comments

Using code metrics effectively

by Rob Galanakis on 19/02/2014

As the lead of a team and then a director for a project, code metrics were very important to me. I talked about using Sonar for code metrics in my previous post. Using metrics gets to the very core of what I believe about software development:

  • Great teams create great software.
  • Most people want to, and can be, great.
  • Systematic problems conspire to cheat people into doing less than their best work.

It’s this last point that is key. Code metrics are a conversation starter. Metrics are a great way to start the conversation that says, “Hey, I notice there may be a problem here, what’s up?” In this post, I’ll go through a few cases where I’ve used metrics effectively in concrete ways. This is personal; each case is different and your conversations will vary.

Helping to recognize bad code

A number of times, I’ve worked with one of those programmers who can do amazing things but write code that is unintelligible to mortal minds. This is usually a difficult situation, because they’ve been told what an amazing job they’ve been doing, but people who have to work with that code know otherwise. Metrics have helped me have conversations with these programmers about how to tell good code apart from bad.

While we may not agree what good code is, we all know bad code when we see it, don’t we? Well, it turns out we don’t. Or maybe good code becomes bad code when we aren’t looking. I often use cyclomatic complexity (CC) as a way to tell good code from bad. There is almost never good code with a high CC. I help educate programmers about what CC is and how it causes problems, giving ample references for further learning. I find that because metrics have a basis in numbers and science, they can counteract the bad behaviors some programmers have that are continually reinforced because they get their work done. These programmers cannot argue against CC, and without exception have no desire to. They’re happy to have learned how they can keep themselves honest and write better code.

It’s important to help these programmers change their style. I demonstrate basic strategies for reducing CC. Usually this just means helping them split up monolithic functions or methods. Eventually I segue into more advanced techniques. I’ve seen lightbulbs go off, and people go from writing monolithic procedures to well-designed functions and classes, just because of a conversation based in code metrics and followup mentoring.

I use CC to keep an eye on progress. If the programmer keeps writing code with high CC, I have to work harder. Maybe we exclusively pair until they can stand on their own feet again. Bad code is a cancer, so I pay attention to the CC alarm.

Writing too much code

A curious thing happens in untested codebases: code grows fast. I think this happens because the code cannot be safely reused, so people copy and paste with abandon (also, the broken windows theory is alive and well). I’ve used lines of code (LoC) growth to see where it seems too much code is being written. Maybe a new feature should grow a thousand lines a week (based on your gut feeling), but if it grows 3000 lines for the last few weeks, I must investigate. Maybe I learn about some deficiency in the codebase that caused a bunch of code to be written, maybe I find a team that overlooked an already available solution, maybe I find someone who copy and pasted a bunch of stuff because they didn’t know better.

Likewise, bug fixing and improvements are good, so I expect some growth in core libraries. But why are a hundred lines a week consistently added to some core library? Is someone starting to customize it for a single use case? Is code going into the right place, do people know what the right place is, and how do they find out?

LoC change is my second favorite metric after CC, especially in a mature codebase. It tells me a lot about what sort of development is going on. While I usually can’t pinpoint problems from LoC like I can with CC, it does help start a conversation about the larger codebase: what trends are going on, and why.

Tests aren’t being written

A good metrics collection and display will give you a very clear overview on what projects or modules have tests and which do not. Test count and coverage numbers and changes can tell you loads about not just the quality of your code, but how your programmers are feeling.

If coverage is steadily decreasing, there is some global negative pressure you aren’t seeing. Find out what it is and fix it.

  • Has the team put themselves into a corner at the end of the release, and are now cutting out quality?
  • Is the team being required to constantly redo work, instead of releasing and getting feedback on what’s been done? Are they frustrated and disillusioned and don’t want to bother writing tests for code that is going to be rewritten?
  • Are people writing new code without tests? Find out why, whether it’s due to a lack of rigor or a lack of training. Work with them to fix either problem.
  • Is someone adding tests to untested modules? Give them a pat on the back (after you check their tests are decent).

Driving across-the-board change

I’ll close with a more direct anecdote.

Last year, we ‘deprecated’ our original codebase and moved new development into less coupled Python packages. I used all of the above techniques along with a number of (private) metrics to drive this effort, and most of them went up into some visible information radiators:

  • Job #1 was to reduce the LoC in the old codebase. We had dead code to clean up, so watching that LoC graph drop each day or week was a pleasure. Then it became a matter of ensuring the graph stayed mostly flat.
  • Job #2 was to work primarily in the new codebase. I used LoC to ensure the new code grew steadily; not too fast (would indicate poor reuse), and not too slow relative to the old codebase (would indicate the old codebase is being used for too much new code).
  • Job #3 was to make sure new code was tested. I used test count and coverage, both absolute numbers and of course growth.
  • Job #4 was to make sure new code was good. I used violations (primarily cyclomatic complexity) to know when bad code was submitted.
  • Job #5 was to fix the lowest-hanging debt, whether in the new or old codebase. Sometimes this was breaking up functions that were too long, more often it was merely breaking up gigantic (10k+ lines) files into smaller files. I was able to look at the worst violations to see what to fix, and work with the programmers on fixing them.

Aside from the deleting of dead code, I did only a small portion of the coding work directly. The real work was done by the project’s programmers. Code metrics allowed me to focus my time where it was needed in pairing, training, and mentoring. Metrics allowed the other programmers to see their own progress and the overall progress of the deprecation. Having metrics behind us seemed to give everyone a new view on things; people were not defensive about their code at all, and there was nowhere to hide. It gave the entire effort an air of believably and achievability, and made it seem much less arbitrary that it could have been.

I’ve used metrics a lot, but this was certainly the largest and most visible application. I highly suggest investing in learning about code metrics, and getting something like Sonar up on your own projects.

1 Comment

Using Sonar for static analysis of Python code

by Rob Galanakis on 15/02/2014

I’ve been doing static analysis for a while, first with C# and then with Python. I’ve even made an aborted attempt at a Python static code quality analyzer (pynocle, I won’t link to it because it’s dead). About a year ago we set up Sonar (http://www.sonarqube.org/) to analyze the Python code on EVE Online. I’m here to report it works really well and we’re quite happy with it. I’ll talk a bit about our setup in this post, and a future post will talk more about code metrics and how to use them.

Basic Info and Setup

Sonar consists of three parts:

  • The Sonar web interface, which is the primary way you interact with the metrics.
  • The database, which stores the metrics (Sonar includes a demonstration DB, production can run on any of the usual SQL DBs).
  • The Sonar Runner, which analyzes your code and sends data to the database. The runner also pulls configuration from the DB, so you can configure it locally and through the DB.

It was really simple to set up, even on Windows. The web interface has some annoyances which I’ll go over later, and sometimes the system has some unintuitive behavior, but everything works pretty well. There are also a bunch of plugins available, such as for new widgets for the interface or other code metrics checks. It has integrations with many other languages. We are using Sonar for both C++ and Python code right now. Not every Sonar metric is supported for Python or C++ (I think only Java has full support), but enough are supported to be very useful. There are also some worthless metrics in Python that are meaningful in Java, such as lines in a file.

The Sonar Runner

I’ll cover the Runner and then the Website. Every night, we have a job that runs the Runner over our codebase as a whole, and each sub-project. Sonar works in terms of “projects” so each code sub-project and the codebase as a whole have individual Sonar projects (there are some misc projects in there people manage themselves). This project setup gives higher-level people the higher-level trends, and gives teams information that is more actionable.

One important lesson we learned was, only configure a project on the runner side, or the web site. An example are exclusions: Sonar will only respect exclusions from the Runner, or the Web, so make sure you know where things are configured.

We also set up Sonar to collect our Cobertura XML coverage and xUnit XML test result files. Our Jenkins jobs spit these out, and the Runner needs to parse them. This caused a few problems. First, due to the way files and our projects were set up, we needed to do some annoying copying around so the Runner could find the XML files. Second, sometimes the files use relative or incomplete filenames, so parsing of the files could fail because the Python code they pointed to was not found. Third, the parsing errors were only visible if you ran the Runner with DEBUG and VERBOSE, so it took a while to track this problem down. It was a couple days of work to get coverage and test results hooked into Sonar, IIRC. Though it was among the most useful two metrics and essential to integrate, even if we already had them available elsewhere.

The Sonar Website

The Website is slick but sometimes limited. The limitations can make you want to abandon Sonar entirely :) Such as the ability to only few metrics for three time periods; you cannot choose a custom period (in fact you can see the enum value of the time period in the URL!). Or that the page templates cannot be configured differently for different projects (ie, the Homepage for the ‘Entire Codebase’ project must look the same as the Homepage for the ‘Tiny Utility Package’ project). Or that sometimes things just don’t make sense.

In the end, Sonar does have a good deal of configuration and features available (such as alerts for when a metric changes too much between runs). And it gets better each release.

The Sonar API

Sonar also has an API that exposes a good deal of metrics (though in traditional Sonar fashion, does not expose some things, like project names). We hook up our information radiators to display graphs for important trends, such as LoC and violations growth. This is a huge win; when we set a goal of deleting code or having no new violations, everyone can easily monitor progress.

Summary

If you are thinking about getting code metrics set up, I wholeheartedly recommend Sonar. It took a few weeks to get it to build up an expertise with it and configure everything how we wanted, and since then it’s been very little maintenance. The main struggle was learning how to use Sonar to have the impact I wanted. When I’ve written code analysis tools, they have been tailored for a purpose, such as methods/functions with the highest cyclomatic complexity. Sonar metrics end up giving you some cruft, and you need to separate the wheat from the chaff. Once you do, there’s no beating its power and expansive feature set.

My next post will go into more details about the positive effects Sonar and the use of code metrics had on our codebase.

3 Comments

Can you grok Agile without a programming background?

by Rob Galanakis on 12/02/2014

The last fourteen years have been strange. The Agile movement, which was spawned out of the controversy over Extreme Programming, skyrocketed into success, and was subsequently taken over by the project managers who all but pushed the programmers out. We’ve seen the creation, the wild success, and the corresponding (and predictable) impotence, of certifications… We’ve experienced continuous and vocal process churn as consultants and authors split and competed over Kanban, Lean, and every new project-management prefix-of-the-day…

Taken fromĀ Extreme Programming, a Reflection, by Uncle Bob

Well, I’m glad I’m not alone in those feelings! But the core question, which will continue recurring, is: can you truly understand Agile, or any development methodology, without having done that most fundamental development work: programming? My gut and experience tells me absolutely not but yet we continue to hand over control of our development methodology to people who have never done programming, personally or professionally. I doubt I am alone in this sentiment.

And then there’s W. Edwards Deming, the stepfather of the modern Japanese automotive industry and Lean movement, who was not in any way initially a car guy, who was a statistician by education and practice. I could dismiss his profound success and influence as due to his brilliance or individual abilities, but I feel it’d be unfair and simplistic. I suspect serious insight can be made by having a “process expert” who is not an expert in the subject matter. A voracious reader, experimenter, learner, statistician. But rare. Not many are needed; those that are should be coaches and not managers.

Deming was disruptive, and unaccepted in America. “No man is a prophet in his own land.” Is the project manager running your standups Deming-like? Is he applying the principles of Lean and Agile, or just the tools of Scrum? Do retrospectives involve questioning the entire system, or have they degenerated to complaints about other teams?

I’m not saying your project manager is doing a bad job, but he’s likely no Deming. So should he be responsible for the implementation of Agile? Agile is, at its core, about creating tools that support principles that govern the way programming and development is done. Why should this be up to someone that doesn’t program, and never has? What outcome do you expect?

Every original signatory of the Agile Manifesto has experience as a software developer (or in one case, tester).

Programmers: take back ownership over Agile and the way you work.

10 Comments