#
Getting Started
This is a course about designing and building software applications. We use the term application
to specifically mean software designed to solve problems for users. Most of the software that you interact with on a daily basis are applications: the weather app on your phone, the spreadsheet that you use for work, the online game that you play with your friends. Applications can include console applications (e.g., vim), desktop software (e.g., MS Word for Windows), mobile software (e.g., Weather on iOS), or even web applications (e.g., Gmail).
The other main category of software is system software, or software that is meant to provide services other other software e.g., operating systems, device drivers. It's important to realize that there is no clear line between applications and system software. For example, a web browser is an application, but it also provides services to other applications e.g., rendering HTML, executing JavaScript. Similarly, an operating system is system software, but it also provides services to applications e.g., managing memory, scheduling processes. These distinctions are not always clear-cut.
#
Characteristics
There are many different forms of application software! We can group applications based on some simple characteristics.
- Target device: Is the software intended to run on a small device like a smartphone or watch? Is it intended for a desktop computer or notebook?
- Interactive vs. non-interactive: Interactive applications wait for user input and respond to it. They tend to run until the user closes them. e.g., vim, PowerPoint, League of Legends Non-interactive applications might accept some initial inputs to determine how they execute, but then run to completion. e.g., Unix command-line utilities like ‘ls’.
- Input modality: Is the software touch-driven e.g. smartphone or bank kiosk? Is it mouse/keyboard driven like a desktop computer?
Some of these characteristics tend to work well together, while others do not. For example, a desktop application is usually mouse/keyboard driven, and is interactive. A mobile application is touch-driven, and is also interactive. A command-line application is non-interactive, and is usually keyboard-driven.
In practice, we have specific sets of characteristics that work well together.
#
Application Styles
An application style denotes a particular combination of characteristics that are commonly delivered together. Let's review the most common application styles1:
#
Command-line
These are applications that are designed to be used from a terminal or console. They are usually text-based and rely in terminal standards for I/O. e.g., ls
, grep
, vim
.
Characteristics of command-line applications include:
- Keyboard driven. They can be interactive (i.e. wait for input), or non-interactive (i.e. run a script).
- Non-graphical. Can be run from a shell e.g. bash, zsh, powershell.
- Use standard input/output. They can read from standard input (e.g. a file, or another program), and write to standard output (e.g. the shell itself).
- Scriptable. They can be used to automate tasks, or to build more complex applications.
Command-line applications are often used used for tasks that require quick and efficient interaction, or for information-dense displays. They are relatively easy to develop, and represent a relatively fast and simple way to deliver functionality. However, they are also error-prone for users compared to graphical interfaces, and require a certain level of technical knowledge to use. Unless you are developing software-development tools (or something similar), they are probably not the best choice for your application.
You've been writing command-line applications in Racket, C and C++ for a while now! We usually learn programming with this style of application first, because it's relatively simple compared to graphical applications.
Console applications can often be written with just the standard library i.e. no user-libraries required. This lets you focus on learning the language itself.
#
Desktop
Desktop applications are graphical applications that run on a desktop operating system e.g. Windows or macOS. These applications rely on mouse/keyboard for input, present information graphically, and are designed to provide a rich user experience. e.g., MS Word, Photoshop, League of Legends.
Characteristics of desktop applications include:
- Keyboard and mouse are used for input. Other pointing devices may be supported e.g. trackpad.
- Windows layered on a
desktop
is a primary abstraction. Application input/output is contained within awindow
, which can be moved, resized, and closed. - Multiple-window support. Applications may have multiple windows, each with their own content.
- Rich user experience. Applications can use graphics, animations, and other visual effects to make the user experience more engaging.
Your choices of specific desktop operating systems are, practically speaking, Windows, macOS or Linux. Each of these operating systems has its own set of APIs, libraries, and tools that you can use to build this style of application.
If you are building productivity software, or software that requires keyboard/mouse interaction, this is likely the appropriate platform. Desktop applications are also most suitable for tasks that require a lot of screen real-estate, or that need to be used for long periods of time.
#
Mobile
Mobile applications are designed for use on a smartphone, tablet or similar device. These applications are often touch-enabled, and are designed for a small screen. e.g., Weather, Instagram, WhatsApp. Although in practice there are design differences based on form-factor (smartphone vs. tablet), we tend to lump them together as "mobile applications".
Your choice of mobile operating systems is practically speaking, iOS or Android.
Characteristics of mobile applications include:
- Touch-enabled. Users interact with the application by tapping, swiping, and pinching elements on the screen.
- Small-screen enabled. Applications are designed to be used on a small screen, and are often optimized for portrait mode.
- Single-tasking. Due to the screen size, applications tend to be single tasking, and are not designed for long periods of use.
- Rich-user experience. Similar to desktop applications, mobile applications can use graphics, animations, and other visual effects to make the user experience more engaging.
Where's the web in this discussion?
Is the web also a platform for consideration? Well, yes and no. It's complicated.
Web applications are applications designed to be delivered and executed on a web browser, which is itself an application that runs on a desktop or mobile device. The web application relies on the browser to determine how it is displayed, how the contents are resized or formatted, and how it interacts with the user e.g. touch vs. desktop.
In other words, the browser is actually the target platform for a web application. This is objectively very different from desktop or mobile applications, which are designed to run on the underlying OS, and have some degree of access to the hardware. Browsers have limited capablities with respect to the underlying OS, and are sandboxed for security reasons. In other words, a web application is functionally very different from a desktop or mobile application.
The choice of native application (targeting the underlying OS) or web application (targeting a browser) is a major decision that you need to make as a developer, and one that we'll consider shortly.
#
Expected Features
Let's assume going forward that we will be building graphical applications, either for desktop or mobile. Here are common requirements for both desktop and mobile applications that we need to address.
#
Graphical user interface
Applications are graphical, and contain interactive elements like buttons, switches, sliders, and text fields. These elements should be easy to use, and should provide feedback to the user.
Interactive interfaces are a major part of a modern application. We will spend considerable time discussing user interface toolkits that make it easy to build these interfaces.
#
Standard interaction
For mobile applications, we also expect to be able to use standard gestures to interact with the application. This can include:
- Tap to select
- Swipe to scroll
- Pinch to zoom in/out
For desktop applications, we should expect standard keyboard and mouse navigation. This can include:
- Keyboard shortcuts. Users should be able to use the keyboard to navigate the application, and to perform common tasks.
- Mouse support. The mouse should be used for most selection tasks, and for manipulating data (often through a right-click context menu, or dropdown menu).
In both cases, users should be able to navigate through multiple screens or sections of the application using a breadcrumb trail, or a series of links.
#
Rich data manipulation
We expect to be able to manipulate data in a variety of standard ways. This can include:
- Cut-Copy-Paste. You should be able to use these commands in both desktop and mobile applications to manipulate text and image data.
- Undo-Redo. You should be able to undo and redo changes to your data. This is a common feature in desktop applications, and is becoming more common in mobile applications (usually based on the features of the application you are building).
- Drag-Drop. When an application requires you to move data from one place to another, you should be able to drag and drop it. e.g. dragging an image from your file system into a dialog box.
#
Database support
We should be able to store and retrieve data from a database. This can include a local database (like SQLite), or a shared online database (like MySQL or PostgreSQL). We should also be able to easily manipulate data into the correct storage format.
We will discuss working with both local and shared databases.
#
Remote services
Modern applications do not exist in a silo; they are expected to interact with other applications and online services to consume or share data. This can include:
- Accessing cloud services. Your application might need to use cloud services like authentication, storage, or machine learning.
- Accessing online APIs. Your application might need to interact with online services like weather, news, or social media.
We will also discuss how to both design and consume services in a later section.
#
Development Goals
Developers often say that they want to produce "well-designed" software. What does that actually mean?
It doesn't take a huge amount of knowledge and skill to get a program working. Kids in high school do it all the time... The code they produce may not be pretty; but it works. It works because getting something to work once just isn’t that hard.
Getting software right is hard. When software is done right, it requires a fraction of the human resources to create and maintain. Changes are simple and rapid. Defects are few and far between. Effort is minimized, and functionality and flexibility are maximized.
— Robert C. Martin, Clean Architecture (2016).
In Martin's view, software should be enduring. Software that you produce should be able to function for a long period of time, in a changing environment. You should expect to make adjustments over time as defects will be found and fixed, new features will be introduced, and old features phased out, but these changes should be relatively easy to make.
What characteristics do we want in our software?
#
1. Usability
A system must be usable for its intended purpose, and meet its design objectives. This includes both functional and non-functional requirements.
- Functional: features that are required to address the problem that the software is intended to solve; desirable features for our users.
- Non-Functional: qualities or characteristics of software that emerge from its structure. e.g., performance, reliability and other quality metrics.
This requires us to carefully ensure that we are meeting all reasonable requirements up-front, typically by collaborating with our users to define problems and solutions. See Process Models.
#
2. Extensibility
You should expect to adapt and modify your software over a lifetime of use:
- You will find errors in your code (aka bugs) that will need to be addressed.
- You might find that requirements were incomplete, so existing features may need to be modified.
- You might uncover new features that need to be implemented.
- Your operating environment might change (e.g. OS update) which necessitates updates.
We need to design systems such that you can respond and adapt your software to these changes, effectively, and with the least amount of work.
Your design goal is not to deliver software once, it's to design in a way that supports delivering frequently and reliably over the life of your solution.
#
3. Scalability
Extensibility refers to handling new or changing requirements. Scalability refers to the ability to handle increased data, number of users, or features. e.g., an online system might initially need to handle hundreds of concurrent users, but could be expected to scale to tends of thousands over time. Scalability is challenging because you don't want to incur the deployment costs up-front, but instead you want to design in a way that lets you expand your system over time. This can include replacing modules with more capable ones at a later time, e.g., swapping out a low-performance database for something more performant.
#
4. Robustness
The system should be able to handle error conditions gracefully, without compromising data. This includes user input errors, errors processing data from external sources, and recovering from error conditions, e.g., power outages.
#
5. Reusability
Software is expensive and time-consuming to produce, so anything that reduces the required cost or time is welcome. Reusability or code reuse is often positioned as the easiest way to accomplish this. Reusing code also reduces defects, since you're presumably reusing code that is tested and known-good.
#
Modern Development
How do you deliver these features, while still producing high-quality software, on-time? What tools and technologies do you need to build modern applications?
#
Platform Choice
Deciding which type of application to build, and what features you want to support, is a major decision for a developer; it will determine the user experience, the capabilities of the application, and the number of users that you can potentially reach. It will also determine which target operating systems you need to support, and the programming languages and libraries that you have available.
How do you decide which application style and platform to support?
Consider the user experience first. What application style is most suitable for your user and their requirements? If you're building an application that is meant to be used on-the-go, then mobile is probably an important platform to support. If you're building a productivity application that required a lot of screen space, then desktop may be more suitable.
Consider the market share of the platform. Mobile is arguably a more common platform i.e. "everyone has a phone". Desktop is still a significant platform, but is in decline.
- Finally, consider the market share of the operating system for your specific scenario. Context is important. Windows has by-far the dominant share of desktop operating systems for general consumers and business users, but macOS does very well with creative professionals including developers. Android has the dominant share of mobile operating systems, but iOS has a significant share of the high-end market.
Your long-term strategy may be to support multiple platforms. Be careful if you do this, since it may require you to build and maintain multiple codebases, and to support multiple development environments. However, it can also significantly increase the number of users that you can reach.
A good example of a modern application is Things, a task management application that runs on desktop, phone, tablet (and even watch). It is cloud-enabled, and provides a consistent user experience across all of these devices. Multi-device support is a major factor in its success.
Another example would be a service like Netflix, which has dedicated applications for Apple TV, Roku, and other set-top boxes, as well as for desktop, phone, tablet and web. This allows users to access their content from any device, and to have a consistent experience across all of them.
#
Programming language
Your choice of programming language is a significant factor in how you develop your application. We can (simplistically) divide programming languages into two categories: low-level and high-level languages.
Low-level languages are suitable when you are concerned with the performance of your software, and when you need to control memory usage and other low-level resources. They are often most suitable for systems programming tasks, that are concerned with delivering code that runs as fast as possible, and uses as little memory as possible. Examples of systems languages include C, C++, and Rust. Systems programming is often used for operating systems, device drivers, and game engines.
High-level languages are suitable when you are concerned with the speed of development, and the robustness of your solution. Applications programming leans heavily on high-level languages, making some performance concesssions for increased reliability, more expressive programming models, and programming language features. In other words, applications tend to be less concerned with raw performance, and so we can afford to make tradeoffs around performance1.
Examples of application languages include Swift, Kotlin, and Dart. Applications programming languages are often used for web applications, mobile and desktop applications. They may also be used when creating back-end services e.g. web servers, or the server side of an application.
Note that is is certainly possible to develop applications in low-level languages, and to develop systems software in high-level languages. However, the choice of language is often driven by the requirements of the software you are building.
We can compare some of the language features of low-level and application languages:
Modern application languages like Swift
, Kotlin
and Dart
share similar features that are helpful when building applications:
Functional programming features. Modern languages feature support for functional features like lambdas, higher-order functions, and immutability. These features can make your code more expressive, and easier to understand. Theses are in addition to the traditional object-oriented features that you would expect from a modern language.
Memory management. Garbage collection is common with application languages, where you want to optimize for development time and application stability over performance. Garbage collection is simply a paradigm where the language itself handles mamory allocation and deallocation by periodically deallocating unused memory at runtime. GC results in a system that is siginficantly more stable (since you're not manually allocating/deallocating with all of the risks that entails!) but incurs a runtime performance cost. The majority of the time, the performance difference is negligible2.
NULL safety. Swift, Kotlin and Dart have null safety built in, which can prevent null pointer exceptions. For example, you cannot access uninitialized memory in Kotlin, since the compiler will catch this error at compile time.
Runtime performance. Application languages tend to be slower than systems languages, given the overhead of GC and similar language features. Practically, the difference is often negligible at least when working with application software.
Portability. Systems languages are often "close to the metal". However, being closely tied to the underlying hardware also make it challenging to move to a different platform. Application languages can be designed in a way that leverages a common runtime library across platforms, making it simpler to achieve OS portability. Note that this is not always the case; Swift, for example, is only available on Apple platforms.
#
OS functionality
Even more important than the programming language is the operating system that you are targeting.
An operating system is the fundamental piece of software running on your computer that manages hardware and software resources, and provides services to applications which run on it. Essentially, it provides the environment in which your application can execute. The core of an operating system is the kernel, the main process which runs continuously and manages the system.
The role of an operating system includes:
- Allocating and managing resources for all applications, and controlling how they execute. This include allocating CPU time (i.e. which application is running at any given time), and memory (i.e. which application has access to which memory).
- Providing an interface that allows applications to indirectly access the underlying hardware, and does this in such as way that each application is isolated from the others.
- Providing higher-level services that applications can use e.g., file systems, networking, graphics. The OS abstracts away the underlying hardware so that applications don't need to be concerned with the underlying implementation details i.e. you don't need to write programs that directly interact with a hard drive, or a particular graphics card.
Applications interact with the kernel by making system calls. A system call is simply a function call through a specific API that the OS exposes. The actual interface will differ based on the operating system, but can typically used to perform tasks like reading from a file, writing to the console, or creating a network connection. OS vendors each provide their own "native" system call interface.
Notice that the kernel of the operating system runs in a special protected mode, different from user applications. Effectively, the core operating system processes are isolated from the user applications. This is a security feature which prevents user applications from directly accessing the hardware, and potentially causing damage to the system.
Programming languages rely heavily on the underlying operating system for most of their capabilities. In many ways, you can think of them as a thin layer of abstraction over the operating system. For example, when you write a file to disk, you are actually making a system call to the operating system to write that file. When you read from the network, you are making a system call to the operating system to read that data.
#
Libraries
You could (in theory) write software that directly interacts with the operating system using system calls3. However, this is not practical for many situations, since you are then tied to that particular operating system (e.g. Windows 10). Instead, we use system libraries
, which are a level of abstraction above system calls that provide a higher-level interface, to your programming language. This is the level at which most applications interact with the operating system.
The diagram below illustrates the relationship between the operating system and applications running on it:
Kernel mode
represents private, privileged, code that runs in the context of the operating system. This code has direct access to the hardware, and is responsible for managing the hardware resources. The OS kernel is the core of the operating system, and provides the fundamental services that applications rely on. Some system-level software can also execute at this level.User mode
represents code that runs in the context of an application. This code does not have direct access to the hardware, and must use system libraries and APIs to interact with the hardware. Applications and many libraries only exist and execute at this level.
The C++ standard library is an example of a system library, which provides a set of C++ style functions that work on all supported operating systems. When you use std::cout
to write to the console, for example, you are actually calling into the operating system using system calls to interact with the underlying hardware. Your code compiles and runs across different operating systems because the underlying functionality has been implemented for that particular OS, and exposed through that standard library interface.
C++ is a special case, where industry leaders standardized this behaviour. However, there is a lot of other OS functionality that differs across operating systems, and doesn't have a standard interface. For example, there is no standard library for creating a window on a desktop, or for accessing the camera on a phone. This poses challenges. Stay tuned.
Vendors and OSS communities also usually provide user libraries
, which are simply libraries that run in user-space and can be linked into your application to provide high-level functionality. Accessing a database for instance isn't handled by the operating system directly, but by a user library that abstracts away the details of the database and provides a simple interface for your application to use.
We'll make extensive use of libraries in this course! We'll use system libraries to interact with the operating system, and user libraries to provide higher-level abstractions that make it easier to build applications.
#
Technology Stack
A technology stack is the set of related technologies that you use to develop and deliver software. e.g., LAMP for web applications (Linux, Apache, MySQL and PHP).
What technology stack is suitable for building desktop and mobile applications? Your application style and operating system will determine what languages and technical stack is practical.
Why is there so much fragmentation across operating systems? The major platforms are controlled by different vendors, their focus is on producing tools to facilitate development on their platforms. Apple wants consumers to run macOS and iOS, so they produce development tools that make it easy for you to build applications for those platforms. They have no interest in helping you build applications for Windows or Android. Similarly, Microsoft focuses on Windows and web development, with limited ability to leverage those tools to build for other platforms.
In other words, each platform has it's own specific application programming languages, libraries, and tools4. If you want to build iOS applications, your first choice of technologies should be the Apple-produced toolchain i.e. the Swift programming language, and native iOS libraries that Apple provides. Similarly, for Android, you should be using Kotlin and the native Android libraries.
Feeling like you're being forced down a path? You should, that's exactly what is happening! For each decision you make, your subsequent choices are being narrowed down.
What if you want to build for more than one platform? You have a few options in this case:
Build a native application for each platform. Use Microsoft's toolchain to build a Windows application, then use Apple's toolchain to build the macOS version (2x the work) and then repeat again for building an Android application (3x the work), and so on. This is what many companies do, since native toolkits tend to provide the most features and highest performance for that platform. However, it is also the most work, since you need to build and maintain separate codebases for each platform.
Use a cross-platform framework. Find a way to leverage code and libraries across more than one platform. This can be done in a number of ways, including using a cross-platform framework like React Native, Xamarin, Flutter, or Compose Multiplatform. This approach can save time and effort, but it can result in a less polished user experience, since you are not using the vendor-preferred tools for each platform.
Give up and just develop for the web. Part of the reason for the success of the web is that you can, theoretically, build an application that runs in a web browser on each platform. This is the easiest approach, since you only need to build and maintain one codebase, but it can also result in a less polished user experience. You also tend to have restricted access to the capabilities of the device e.g. the camera or the GPS.
#
What's Next?
Here's what we're going to do in this course.
We want you to have the option of mobile or desktop development. We also need to make sure that everyone in the course can run the development chain, which means that we cannot use the Apple toolchain (which is only available to people using macOS). This means that we're going to use Kotlin and build Android applications or desktop applications.
Kotlin is the main programming language for Android development, and really useful when targeting that platform. JetBrains, the company behind the language design, is working with Google to expand support to desktop, web and eventually iOS, so that you can use a single toolchain to build for all of these platforms!
Kotlin is a modern, application focused programming language with a lot of amazing features. We'll learn how to use Kotlin to build well-designed applications that meet the goals we've outlined. This includes discussions of architectural styles, useful design principles, design patterns and best practices like pair programming, unit testing and code reviews.
We'll also discuss how to implement key features like user-interfaces, database connectivity and web services using industry-standard toolkits e.g. Compose for UI.
Finally, we will do all of this in the context of best team practices. We will take steps to ensure effective team communication and collaboration, and identify things that you can do to work effectively together. These are extremely important skills to develop as we start building larger and more complex systems.
-
There are also other specialized application styles e.g. cars, wearable tech like watches, public kiosks, and game consoles. We'll focus on desktop and mobile applications in this course.↩↩
-
Kotlin GC runs in less than 1/60th of a second, and only when necessary. Dart GC runs in less than 1/100th of a second. Users certainly don't notice this.↩
-
My first Windows program was created in C, making system calls to the Win32 API i.e. the underlying OS. It was incredibly painful, and I don't recommend it.↩
-
Yes I am aware that Swift, for example, can be used to build applications on Windows, but only in the most limited way i.e. you're restricted to simple console applications, or working with native interop calls to the underlying APIs. No sane developer would actually do this.↩