Category Archives: Programming

general programming articles

How to work with freelance software developers

I have been freelancing as a remote developer for over ten years. I specialize in legacy systems and taking over abandoned and unfinished software projects. I see the problems customers have hiring and working with freelance developers. Here’s my advice for avoiding some of the potholes.

This article is about freelance software developers, programmers, and system administrators, because that’s where my experience is. Some of this applies to freelance designers, writers, marketing consultants, etc. I work through 10X Management, a talent agency for developers and technology professionals. I am available to consult with customers to help them find, hire, and manage freelance developers, and to define and prioritize requirements and measure progress.

Interviewing, hiring, and managing

I’m not going to get into step-by-step instructions regarding the nuts-and-bolts of finding, hiring, negotiating with, and paying freelancers, writing contracts, tax laws, etc. If you aren’t confident that you can evaluate and manage a freelance developer you should use an agency or hire a consultant who has that experience. Software development is expensive and takes time, managing programmers is hard.

Personality conflicts will kill your project and stress your team. Over and over I get involved in situations where the customer and the freelancer clashed personally. If you don’t feel comfortable with the freelancer when you interview them, find someone else. Be aware of your own rough edges and prejudices. Get someone else involved interviewing and managing freelancers. If you won’t be the main person working with the freelancer, get the people in your organization who will be involved in the selection and hiring, and pay attention to any objections they raise. Hiring someone without getting buy-in from the rest of your team gets everyone off on the wrong foot.

Many contract programming shops outsource most of their projects. Be sure you are talking to the people who will be working with you and your staff, not just an account executive or project manager who closes the deal but doesn’t do the work.

Be clear about your expectations, but back off any you can’t justify. For example you may feel strongly that you need someone working on site every day. That requirement will greatly reduce the pool of available freelancers. Is that an absolute requirement (for example, security clearance required, systems that can’t be accessed remotely, specialized hardware, etc.), or is it worry about not having control? Here are some things you should be clear about before you start interviewing freelancers:

  • Your budget, total or for the first set of defined tasks.
  • Your schedule.
  • On-site time required? How much/how often?
  • Who will the freelancer report to and work most closely with?
  • Is a security clearance or background check required?
  • Availability — how fast do you expect phone calls and emails returned? What qualifies as an emergency?
  • Who owns the completed product?
  • Do you have existing systems that are part of the project?

Before you start interviewing freelancers you need to have some high-level requirements defined. You should be specific without getting into unnecessary detail, and that takes some practice and editing. “Web developer needed for golf equipment retailer fulfilling 10,000+ orders per month, must interface with existing back-end system” is a lot better than “PHP and MySQL programmers with 3+ years experience.” Try to be detailed enough to attract people who will be a good match for your needs, but not so specific that you eliminate too many candidates. Avoid listing technical requirements (languages, tools, years of experience) unless they are absolutely necessary for the job, and you are able to evaluate the freelancer’s competence.

Check references and reputation

When you find a freelancer you like ask for examples of similar projects and references you can contact. Almost no one is going to put you in touch with their unhappy customers, but you should be able to get an idea of the freelancer’s history and reputation by talking to their previous customers and checking them out online.

A nice online portfolio, blog, customer testimonials, and a github profile are all things that a professional developer should have, but those are not sufficient to base a hiring decision on. Has the freelancer worked on projects of similar scale and scope? Online study assignments and side projects indicate enthusiasm but not necessarily professional skills or ability to deliver, so set your expectations (and payment) accordingly.

Business domain experience is at least as important as technical skills

Look for experience in your business domain, or a similar domain, and value that experience as much as checklists of technical skills. You probably are not qualified to determine how proficient someone is with PHP or Ruby. You can judge how much they know about your business domain, or a related domain. For example if you are looking for someone to work on an e-commerce site talk about inventory management, shipping charges, sales tax, payment processing, customer management. If the freelancer doesn’t understand your business or doesn’t ask questions about how it works your requirements aren’t going to make sense to them. Technical skills alone do not solve business problems.

I have sat in on many interviews where the customer and the developer talk past each other. The customer is trying to describe what they need in terms of business requirements, and the developer is talking about languages, tools, scaling, programming process. This disconnect starts at the beginning of the relationship and will, at best, lead to a false start or two, and at worst to a failed project. If you aren’t communicating with the freelancer or you don’t get the impression that they care about and understand your business needs, address that problem early on or find someone else.

Be alert to signs that the developer is going to use your project as an opportunity to try new programming languages or tools. Unless you want to be on the bleeding edge beta testing open source software insist that the developer use stable, mature tools that everyone else is using. It will be a lot easier for you to find people to work on software that is built with mainstream tools.

Define the tasks and deliverables

After personality fit, the biggest determinant of success or failure is how well you describe the tasks and deliverables. This is actually the hardest part of any software development project, so take some time to get it right. Get professional help if necessary. You need to break your project down into small well-defined tasks with clear deliverables. “Set up e-commerce site for my business” is not a specific requirement. What you want to end up with is a list of measurable tasks, in order, so the freelancer always knows what is next and you always know what has been completed. Every task should have an associated deliverable that clearly describes what “done” means for that task.

Defining requirements and setting priorities is one of the hard problems of software development. There are several techniques for managing requirements. Some people prefer the so-called waterfall or BDUF (big design up front) approach, others advocate agile techniques, where software is development in small steps so gaps in requirements can be discovered early without causing a lot of disruption. It’s important to recognize that most customers and most programmers are not very good at gathering and defining requirements, so be realistic. (It’s also important to recognize that no development process guarantees success.)

The best way to protect yourself from fights about missed and vague specifications and changing requirements during development is to start by describing the simplest possible thing that will work. Developers call this the minimum viable product, or MVP. You don’t have to define all your requirements in advance: you only need to define enough tasks to get started. With a list of small well-defined tasks and measurable deliverables you can quickly assess if the developer is working out as expected. You will discover misunderstandings and problems early on, and take action to get things on track or terminate the relationship. If you start with a big list of vague, incomplete requirements and let the developer work in isolation for months you won’t know for a long time if the project is is in the weeds, and the developer won’t know if they are not doing what you expect.

It’s hard to resist doing more than you originally planned when the programmer is telling you how easy it will be to add features, make your system into a platform with APIs, scale to Facebook-level traffic. Don’t go down the path of scope creep, it leads to unfinished projects. Insist that the developer do the simplest possible thing to meet requirements, and don’t let them expand requirements to account for things that aren’t likely to happen.

Communicate clearly and frequently, and don’t get caught up in process

Establish ground rules with the developer regarding communication, and be sure you follow those rules. If you expect the developer to answer phone calls and emails within an hour, you should do the same. I hear customers complain about developers who don’t respond to calls and emails for days or weeks. I also have experience with customers who don’t reply promptly, or at all, to questions I send, or who don’t deliver things they are responsible for on time. If you aren’t answering questions and giving the developer what they need your schedule will slip, and you will frustrate the developer. If you delegate day-to-day management or tasks like approving designs or producing graphics to someone else, make sure those things are getting done.

The developer may propose (or even demand) that you use their process and tools. If that makes sense to you, great. But be wary of developers who have a lot of process and make that your problem. As the customer you should be able to discuss requirements and report bugs in person, over the phone, or in an email — don’t agree to only communicate through online project management or bug reporting tools. Whatever process and tools the developer needs to use to deliver is their internal issue, it should not be forced on you, and you should not be paying for time spent on internal processes that don’t have a clear and measurable benefit to you.

Build incrementally.

You should agree with the developer that you need to have something working all the time. Don’t allow for weeks or months of development with no visible results — insist on small steps that build the system up bit by bit. Even if you start with a single web page that shows your logo and some mocked-up functionality, that’s better than having nothing to look at. You should also insist that the developer use source code control from the beginning (Subversion or git), that you have separate development, staging, and production environments, and that you are not using real customer information or credit card numbers for development or testing.

Pay for results, not fixed-fee or hourly

Another reason to work from a prioritized list of small tasks is payment. I’m astonished that customers and freelance developers agree to fixed-fee proposals with significant money and time at stake, and the entire project is described in one or two pages of exhibits tacked on to the contract. Unless you have a history with the freelancer that gives you confidence that they will deliver on time and on budget, or you are buying a mostly off-the-shelf solution with very little integration and customizing, a big fixed-fee contract is probably not a good idea. If you go into a project with a fixed-fee arrangement there’s a good chance you won’t know about schedule or cost overruns until late in the project, and there’s also a good chance you will be presented with costly change orders for every small change. Even experienced professional software project managers can’t anticipate all requirements or estimate tasks reliably. Don’t expect that you or the freelance developer will be the exception.

Paying a freelancer by the hour seems like a better plan when you don’t have experience working together. The problem with paying hourly is that deliverables become by-products. The focus on hours spent and time tracking can consume a lot of energy on both sides and lead to arguments about how much time something took or should have taken. I advise against paying hourly, but if you do pay hourly make sure you put a cap on the number of hours you will pay for.

Ideally you should pay for results: finished tasks and working deliverables. That only works if you defined your tasks and deliverables well enough so the freelancer can estimate the work, and you can determine if the estimate is reasonable. As I mentioned earlier, you don’t have to define the entire project in advance and break all of it down into small measurable tasks before you start; you can do that incrementally as the project proceeds. It’s easier for a web developer to estimate “Home page with our logo, main navigation menu, and content areas blocked out, no functionality” than “Start on e-commerce site, show progress in two weeks.” And it’s easier for you to see if the developer is getting the work done as expected and following instructions.

When I start working with a new customer I ask them to list their top five or ten problems, in order, most painful first. If the list is vague I work with the customer to make it more specific: “Web site slow” becomes “Searches with more than 100 results slow to display.” The goal is to have a list of problems we both understand, so I can work from the list, estimate the time I need to fix the problems, and the customer can determine if I’ve delivered.

I prefer to establish long-term relationships with customers and negotiate a retainer agreement, where the customer pays every month for a block of my time they can use for consulting, new development, enhancements, or maintenance. Once you have established a good working relationship and trust with a freelancer working out a retainer is a good way to insure they are available when you need them, and to keep your software development and maintenance costs predictable.

PHP MVC: Maintenance Very Costly

I mostly work on legacy web applications in PHP + MySQL. Usually the original developer is not available, so I have to figure out the code so I can fix problems or add features. For a long time most of the PHP code I worked on was written in the classic PHP style: URLs for each page, PHP code and HTML (and Javascript) all jumbled together, business logic and presentation mixed in the same file. A few years ago I started to see more MVC-style code based on frameworks like Zend, Symfony, Laravel, CodeIgniter, etc. I thought this was a good thing, and that maintaining PHP code that was based on an MVC framework would be easier. My experience, however, has been just the opposite. The classic PHP style is easier for me to understand and refactor even when it has degraded into spaghetti code with PHP and HTML mixed together. It’s easier to work on classic PHP, even badly-written code, because everything you need to know to follow the request/response flow is in one place and reads top to bottom. By comparison trying to understand the thought process behind an MVC application, with the OOP baggage it usually entails, is an order of magnitude harder, and the MVC/OOP code is not necessarily higher quality, more maintainable, or more reliable.

MVC and web applications

MVC (model-view-controller) is a pattern or style of application design that dates back to 1970s and 80s. The idea is to enforce a separation of concerns, decoupling the user interface from the business logic and data persistence layer. MVC doesn’t require OOP (object-oriented programming), but in practice most MVC frameworks are implemented with classes and objects, because OOP can also support strict separation of concerns. There were MVC-style frameworks for PHP before Rails came along, but looking at the current popular PHP frameworks it’s obvious that Rails has inspired and influenced a lot of them.

MVC dates from the pre-web time of mainframes and dumb terminals, and long-running server processes that could update the user interface at any time. The web’s stateless request/response model meant giving up long-running server processes and dynamic interface updates. It’s common for Java- or .NET-based applications to maintain and coordinate state across requests and user sessions, and it’s now possible for the server to push updates to the client (web browser). That’s not how PHP works, though.

So-called MVC frameworks for web applications generally aren’t strictly MVC. Instead they use a router-handler-template-model style that superficially looks like MVC, but doesn’t have some of the important features of MVC. For example web framework “models” are usually dumb layers over a database to do CRUD (create, read, update, delete) operations. Business logic that probably belongs in the model is usually pushed into the controller, and models usually don’t notify the controller or view when something changes in the model. That doesn’t fit the CRUD paradigm or how request/response PHP applications work.

If you look at PHP MVC frameworks what you find are big class libraries that implement some flavor of MVC. The class libraries don’t model a business domain, so the business logic is forced into the framework’s structure, rather than the framework supporting the business logic. Trying to figure out where the business logic should go—controller? model? view?—is a big source of confusion, with no clear right answer. If the main advantage of using a framework is starting with a structure to hang the business logic on, why is it so hard to figure out where that business logic should go?

My experience with PHP frameworks

I’ve used several PHP frameworks in my own projects, and encountered them in code that I’ve taken on for maintenance and enhancement. I’ve written my own PHP framework that is deployed in a couple of real web applications. In every application I’ve run into the same thing: business logic scattered around at all levels, and difficulty figuring out exactly where something happens. Separation of concerns shouldn’t mean fragmentation of concerns, but that’s often what happens.

Real example: Where exactly should I put the business rule that says my client doesn’t accept American Express cards at checkout? In the template, where there’s a Javascript function to checksum the card number and figure out the card type? The template validates things like required entries and which countries my client does business in—why not which credit cards they accept? Or does this rule properly go in the controller that accepts the card number the user entered? Does obtaining an authorization from the merchant processor through an external service API belong in a controller or a model? What if my client decides they will accept AMEX for purchases over a certain amount? Even simple real-world business logic like this doesn’t always have an obvious right place, or it may have to go in several places for usability reasons (or to avoid submitting transactions that the processor will reject, because there’s a penalty for doing that too often).

Another real example: I inherited a web app that displayed a single page of Instagram and Twitter posts matching a handful of hashtags and authors. The previous developer (whom my client had fired after the application was delivered very late and buggy) had chosen to use the Zend Framework 2 for what seems like a fairly simple task. The code was classic OOP ravioli code: many classes with methods doing very granular operations. It was hard to figure out where anything happened because the overall control flow was not apparent, and state was scattered among many objects that had implicit relationships. All of this was wedged into ZF2 for no clear reason. This was tens of thousands of lines of opaque code that didn’t work. I replaced it with a lighter-weight page that just did what it was supposed to do: call the Instagram and Twitter APIs, provide some simple caching, and generate a page of posts. Adding authors or updating the hashtag list turned into simple one-line changes in a config file. The result was less than 1,000 lines, including comments.

This doesn’t mean that every application based on a PHP framework is poorly written or hard to maintain. It means that I’ve worked on a lot of framework-based applications and I’ve seen the same problems again and again.

Is everyone doing it wrong?

Before I started working on web applications I spent over ten years developing and working on OOP enterprise and educational applications, mostly in C++. When these projects went into the weeds or failed, or turned out to be very hard to maintain and extend, the programmers would always blame it on a poorly-designed class structure. If we (or the original developers) could just model the domain correctly in classes and objects everything would be great. The OOP paradigm was never doubted; it’s just that most programmers apparently are too inexperienced or lazy to do it right.

Read the threads online where someone asks how to implement something “correctly” in their PHP framework. If you put any business logic in the template (or view) you are doing it wrong. If you emit HTML from the controller you are doing it wrong. If the model “knows” about user interactions you are doing it wrong. Do business rules that affect presentation, like shipping options based on order total and destination, go in the presentation layer? Do external services like credit card processing or calculating shipping charges go into the model or into the controller? How do I know when I’m doing it wrong when there’s no agreement on how to do it right?

Programming is subject to fashion and religion, based on opinion, faith, and fear of looking stupid or inexperienced. Pick a programming topic and you can read all day about the right way to do it, or a criticism of a supposedly wrong way. These arguments seldom have any foundation based on facts or studies—they are anecdotal and biased. Programmers can be just as certain that their favorite approach is the one right way as they are about spaces versus tabs in source code, with exactly the same facts to base their arguments on: none. The only time I take the right way/wrong way discussions seriously is when they are about algorithms or relational databases, because there’s an actual theoretical and mathematical foundation to base opinions on.

My return to the “mullet style”

Recently I took on a ground-up development project, something I rarely do. Originally it was a debugging job, but the WordPress-based application was such a mess that I had to tell my client they’d be better off starting over, and I don’t tell clients that very often. I decided to revisit my simple MVC framework and improve it with the latest PHP features. I had a single entry to the application to handle global configuration, class autoloader, security, session management, and request routing. I had models that did actual business logic operations on the database (not just CRUD). I had controllers that would process inputs, update the models, and set up variables for the templates. And I had templates that could only contain simple PHP if/then/else and loops. Referring to models or knowing about business rules implemented elsewhere was not allowed. I tried hard to work within the framework and the rules I had made for myself. I refactored the framework two or three times to implement pure solutions to problems I ran into. I had over 7,000 lines of PHP code, a lot of it framework boilerplate and moving data around to enforce separation. It worked, but I was unhappy with it.

I looked at other frameworks, assuming I was doing it wrong, but my architecture was very close to the other leading frameworks. I got some good ideas but never shook the feeling that I was trying too hard to force the application into the framework, and spending a lot of time enforcing what I thought was the right way (or at least not the very wrong way that would invite abuse on StackOverflow and github).

What finally pushed me to abandon the framework approach was realizing that I was flipping through too many files to follow the flow of control, and that too much of my code was there only to comply with the framework’s separation of concerns. Having business logic scattered around in multiple files and classes is painful because I work on a 13″ laptop with vim. Maybe MVC frameworks are easier to work with on multiple 23″ screens, but on a small screen the buffer changes and screen splits cause too many mental context switches. I wanted to see everything that was happening on a single page, so I could keep it in my head and think through it.

Now I have under 4,000 lines of PHP written in a more readable and, I think, maintainable style. URLs map to individual files (with some simple Apache rewriting to support pretty URLs like /email/inbox mapping to email_inbox.php). Every PHP file that corresponds to an HTTP request is written in the classic PHP “mullet” style: business in the front, party in the back. Request handling and business logic, including querying/updating the database through the models, is at the top of the file, followed by HTML that can only use variables defined in the same file and a handful of global utility functions. There’s no embedded SQL or database operations—that’s still in the models, and once the boundary is crossed from PHP to HTML there’s no reference to model functions or any embedded blocks of PHP code. It’s now easy to find where every request is handled and to follow it through to the response (HTML output) in a single file. Common PHP functions and HTML code (page headers and footer, style sheets, Javascript) are in separate files. There’s no more boilerplate, and no more long blocks of code that simply copy validated user inputs or a database query result into a structure to pass to a template renderer.

Of course this is all just my opinion, based on my own experience, biases, and work style. If PHP MVC works for you, great, but you may be surprised to find it’s not as robust or maintainable as you think it is. There’s still no silver bullet when it comes to software development.

Postscript: Don’t throw the baby out with the bath water

I’m not advocating writing spaghetti code or ignoring good programming techniques. It’s possible to write good or bad PHP code whether you use a framework or not. Keep in mind that the popular frameworks do some useful things for you that you will have to do yourself if you don’t use a framework. You have to protect your application from common attacks: SQL injection, XSS (cross-site scripting), CSRF (cross-site request forgery), session hijacking, cookie tampering, insecure API endpoints and AJAX functions, etc. Learn about these attacks and use PHP’s built-in functions or a library you trust to protect your application. Treat all inputs as suspect, whether they come from an HTTP request, a cookie, a database query, or an external service. Use a database library such as PDO, never interpolate variables into a SQL statement. Sanitize everything that is output as HTML or JSON. Make sure your web server blocks attempts to access git or svn repositories, or anything else in your document root that shouldn’t be accessed. Build in error reporting and logging from the beginning. Don’t use global variables. Keep it clean and simple, err on the side of caution.

The things you need to know to do web development

Here’s a list of things I know, or at least know about, as a web developer. I’m sure I’ve left a lot of things out. Web development is a large and complex collection of technologies, tools, languages, protocols, and services. I started programming for the web back in 1995, so I’ve been able to adapt to changes and learn new tools as they were released. If I had to learn web development from scratch today I’m sure it would take me a long time to master even a few of these things.

If you are doing a new project from scratch you’ll get to pick and choose your tools and “stack.” If you are coming into a team that has already made these decisions you’ll have to adapt. If you work with legacy code you’ll have to get familiar with more languages, frameworks, libraries, technologies, and programming styles.

Web applications usually have two big pieces: the front-end code that runs in the browser, and the back-end code that runs on the server.

The front end (browser side)

You’ll need to know HTML, in detail. HTML5 is the latest but you’ll probably have to know how it’s different from HTML4. You also need to know how styles work: CSS, the box model, inline styles, inheritance, specificity, resets, the differences across browsers and browser versions. You will probably need to know about grid systems, and responsive layouts. Most likely you’ll need to learn one or more of the popular front-end frameworks: Bootstrap, Foundation, Toast, Yaml, etc. You may have to know about CSS preprocessors, like Less and Sass.

Almost all modern web applications use Javascript, a lot, so you need to know Javascript. You will probably have to learn jQuery because it’s everywhere. There are other Javascript frameworks and libraries: React, Angular, Ember, Backbone, Prototype, YUI, GWT. You will have to be careful that the Javascript tools play well together, and they work with your HTML/CSS framework. All of the popular frameworks and libraries offer add-on modules and extensions for things like input validation, modal dialogs, drop-down menus, animations, and so on. You will have to learn some of those, too. Javascript manipulates the contents of the web page through a standard abstraction called the Document Object Model (DOM), so you need to know how that works. Browser events such as clicks and keyboard presses can invoke Javascript event handlers. To interact with the back-end server from Javascript you will need to understand AJAX. Once you start using Javascript you will want to learn how to use the browser developer tools, DOM inspector, and debugger.

If you’re lucky you have some design skills, or you are working with a designer who understands the web. Otherwise you’ll be responsible for layout, typography, colors, image formats and optimization, for a start. Customers often don’t understand that a web page is not a Photoshop proof, that they can’t put a 40MB TIFF file on their home page, or why their favorite font is not available to everyone else.

You probably have to know something about the different kinds of devices people use to browse the web and how that affects design and front-end implementation. Desktop and laptop computers, tablets, and phones have obvious and subtle differences. There’s no hovering on most touchscreen devices, for example. High-resolution Retina or 4K displays may give you grief. Mobile users often have slow connections and data rate caps and limits, so you need to think about that and test on all kinds of devices, with wi-fi and cellular connections.

Somewhere along the way you need to understand the HTTP protocol, at least what request/response and statelessness means. You need to understand cookies, browser caching, expiration, content types and MIME. You have to know about mixing content retrieved with HTTP and HTTPS. You will probably need to understand synchronous vs. asynchronous requests.

The back end (server side)

The server stores and serves the HTML and CSS and Javascript files to the browser. A web site that only has HTML, CSS, and Javascript is called a static site. Non-trivial web applications have code running on the server to generate the front-end code dynamically based on user inputs, session state, database queries, and external services. The web server may run one of the general-purpose web servers such as Apache, Nginx, IIS, Tomcat, or it may integrate the server with the application, like NodeJS. There may be multiple web servers handling different types of content: proxy servers, CDNs (content delivery networks).

The web server runs code that may be written in any number of languages, and many web applications use more than one language. PHP is widely used, so is Ruby with the Rails framework, Java, the various .NET languages (C#, VB.net, F#), Javascript with NodeJS. You may run into code written in Perl, C, C++, Go, Erlang, Python, or something else. All of the big server-side programming languages have extensive libraries and components you will encounter or want to use.

Almost all web applications use a database or some kind of datastore accessible by the web server. MySQL, MS SQL Server, PostgreSQL, SQLite, Oracle, DB/2 are all relational database management systems (RDBMSs) that use the relational model and some dialect of the SQL language. You may use a non-relational (so-called NoSQL) database, and there are many of those: MongoDB, CouchDB, Cassandra, Redis, Memcached. Many web applications use more than one database or data storage tool. The database management stuff may be partially hidden behind an ORM provided by your application framework, but it’s likely you will eventually have to understand the details of how your data is stored and retrieved, how data integrity is assured, and how the data is backed up and secured.

External services give you access to all kinds of things: sending and receiving email, credit card and check payment processing, analytics, maps and location services — the list is endless. If your web application uses external services you will have to learn about APIs, REST, authentication, JSON, XML, RPC, SOAP. You may have to use or write a scraper or crawler that extracts data from sites that don’t offer an API. You’ll probably have to work with curl/libcurl at some point when getting your server to talk to an external service.

Besides the languages there are programming techniques, styles, and workflows you will have to understand: object-oriented programming (OOP), maybe functional programming, certainly imperative programming, test-driven development (TDD), Agile, SCRUM, etc. You may need to use a debugger for the server-side code.

The web server has to be hosted somewhere. It may be a server in the office, or co-located at a hosting facility. More likely it will be a shared, dedicated, or managed cloud instance at Amazon, Rackspace, Google, Microsoft, or any number of cloud hosting providers. Hosting decisions can limit your choice of languages and tools, how much control you have over the server configuration, and things like email handling, DNS, bandwidth limits, and so on.

Security and legal/regulatory issues

As soon as your application goes live on the public internet it will be attacked by bots and hackers. You have to understand common vulnerabilities like SQL injection, cross-site scripting (XSS), cross-site request forgery (CSRF), cross-domain request issues, CORS, session hijacking, spambots. If your employer or customer doesn’t have a system administrator you will have to know about firewalls, keypair authentication, and securing your servers. You have to keep up with security threats and patches to the server operating system and every part of your stack.

If you accept credit cards or electronic payments on your site you will be subject to PCI compliance audits. Other types of personal data you collect and store may be subject to other compliance and audit rules (HIPAA, COPPA). Every country your site does business in will have its own legal and regulatory requirements.

Miscellaneous

You will need to master at least one text editor. You will have to know how to get your code uploaded to a server with FTP, SCP, or some other deployment tool. You should be using source code version control: git, subversion, Mercurial. Eventually you will have to learn your way around UNIX, the file system, the shell, tools like find, grep, vim, emacs, nano, ssh, cron.

You will have to understand domain name registration, DNS, probably SSL certificates. You will probably want to monitor your server for crashes and outages and spikes in activity than can signal a denial of service attack or something else wrong.

If your site is available in more than one country, or your customers may not all be English readers, you have to know about internationalization (i8n) and localization and deal with translations and multi-lingual content. You will also have to understand timezones and date/time conversions. Depending on where your users are you may have to deal with country-specific issues, like China blocking most Google contents (goodbye Google-hosted fonts and Javascript libraries).

All of the languages, libraries, frameworks, and tools you choose will be updated and patched frequently, so you have to stay on top of that while making sure updates don’t break your application. Even moderately-complex web applications can have tens or hundreds of dependencies at different layers in the stack. Dependency management and managing versions and updates can turn into a risky and complicated problem all on its own, and of course there are tools to learn for that.

A large number of web sites are based on content-management systems: WordPress, Drupal, etc. You may have to know the advantages and shortcomings of those platforms, along with a lot of the stuff listed above.

If your application get significant traffic and users you will need to think about bandwidth (especially costs), performance, scaling. Decisions that made sense during development or with little traffic can come back to plague you when your site gets loaded.