We recently released the first version of CodeGrade's very own editor! In this blog post, learn about the process of creating this exciting feature.
In the latest release of CodeGrade we’ve introduced a built-in editor that allows students to code right inside CodeGrade. In this blog post, we will explain how we created this new feature and how good design decisions can make your code easier to reason about, and more scalable.
Why an editor?
As always when designing new features for CodeGrade, we first try to find out why we are building a feature. Which type of coder are they, how will this help their educational journey and what extensions to the feature can we already foresee? To get the answers to these questions we write user stories on how the feature will be used. For the editor, we see the most value for the “Coding to understand” group, the people that learn coding to communicate with coders, not to become great programmers themselves. The editor will replace the need for them to set up a local environment, especially when combined with AutoTest. These users are used to tools like Google Docs, not Emacs, so features like auto-save are important but extending the editor with custom code is less important. Furthermore, for this group the best feedback could be offered not just by looking at the end product, but also by looking at the process of how they got there, so keeping edit history and watching them code in real time could be useful additions.
At this point we have a list of requirements for the feature, both functional (i.e. the editor should automatically save work) and non functional (i.e. the editor should look good inside the LMS). This means we can start designing the needed architecture. Before this feature CodeGrade clients relied fully on a REST API to communicate with the backend. This allows us to keep thinking in a request response model, and makes it easy for our customers to create custom scripts with our API. It was clear from the start that a REST API would not work, any collaboration feature would not be possible (polling for changes is not a scalable solution), and automatically saving would introduce an unacceptable amount of latency. To overcome this issue we decided to incorporate web sockets into our stack, this way we can have lower latency communication with our backend, and notify the client of changes. This brings us to the architectural diagram as pictured in figure 1.
The next challenge we have to tackle is how to store the edits to a file. It is really inefficient to store files you are editing as plain text files, as that means that for every inserted character you would need to rewrite the entire file. For our design goals it would have even more disadvantages - keeping edit history would mean that we would have to store every version of the file, and concurrent editing would not work at all. And, as it is so common we come back to the 5th rule of programming by Rob Pike: data dominates: we have to find a suitable data structure for our problem. Luckily we are not the first with this issue and there are two industry standard data structures for our problem: Conflict Free Replication Trees (CRDT) and Operational transformations (OT) 1. These both model a document as a series of edits, which when applied result in the final state of the document. The advantage of this model is that changing the document only means we have to store that edit, and that edit history is preserved by default. The difference between CRDTs and OTs is what an edit is.
1 Technically this is not a data structure, but for the purpose of modeling we can treat it as one.
“Data dominates. If you've chosen the right data structures and organized things well, the algorithms will almost always be self-evident. Data structures, not algorithms, are central to programming.” – Rob Pike
For OTs an edit uses the definition we are all the most familiar with. An edit is a pair of a location and an operation and content or length, so for example an “Insert ‘aa’ at index 56” could be an OT edit. This however, brings a problem, what if two users insert something at the same time? Now the second edit needs a new index to preserve the original meaning. This is where the name “Operational transformation” originates from, on concurrent edits we might need to transform the operations. It is out of the scope of this blogpost to describe exactly how this happens (an excellent visualization is available here), but it does mean that every edit needs to be acknowledged by a central service that knows the current state of the document.
CRDTs work with a bit more advanced representation of edits. Again, we don’t have space to go into depth about how exactly they represent edits (see this paper for an excellent summary). But in essence instead of the operations being relative to the entire document (e.g. insert at index 65), they are relative to characters that are uniquely identified, and they contain a (monotonically increasing) sequence number. This allows edits to have two important properties: they are idempotent (they can be applied multiple times without changing the result) and they are commutative (they can be applied in any order). This means that storing the edits is simple, we can just append them to some kind of list (and the order of this list doesn’t even need to be preserved), and that notifying other users is also simple, just sending the update is enough.
We chose to go with CRDTs. The need for a central server wasn’t an issue, but the advantage of CRDTs is that the server can be quite simple which is nice. CRDTs are also more capable of dealing with clients temporarily losing internet connection, which is nice while working on the move. Finally, with modern CRDT implementations the performance of the two are quite comparable.
Putting it all together
We now have a scalable architecture and a fitting data structure, so just put it together and we are done right? Well not quite yet! We also need to design the API to structure for our web socket API, and design the front-end. We will talk more about the design of our web socket API in a future post. The UI and UX design of features in CodeGrade is always challenging. We have customers using CodeGrade on 4K or ultra-wide screens (still a big debate in the office over which is better), but also users using CodeGrade through an iframe inside their Learning Management System (LMS) on a laptop screen. Furthermore, we have power users that want to optimize their workflow as much as possible, and more casual users. For larger features, such as the editor, we try to release a minimally viable product (MVP) first, and see how our users actually use the product and optimize the experience based on that.
To maximize the amount of space available for the code users are writing we’ve decided to differ a bit from the standard layout of CodeGrade pages. The sidebar is still there, but we’ve removed both the header and footer. The header is replaced by an extensible side pane which also houses the sidebar.
For the implementation of the syntax highlighting and auto completion we can stand on the shoulders of a giant: Monaco, the same technology that powers VS Code. This allows us to support multiple programming languages even in our MVP.
Closing thoughts and future plans
We are really happy to have released our initial version of the editor, and are looking forward to the reports of the first customers working with it. This doesn’t mean the feature is done of course. What exactly we are going to add depends on the feedback we get. But, the nice thing is that with our design process we’ve opened many opportunities for future development. Editing together with other people is already possible, but we’re interested in making that even better. We already have the entire edit history, and we are investigating different UI’s to expose that information to teachers to use while grading. We are also working on making the UI better for power users by allowing advanced features such as splitting the code viewer, so you can edit a document while reading another. Finally, we are really excited by the option to add support for Jupyter notebooks, which would make our platform even better for data science education.
The CodeGrade Introduction to Python course is an 8-week basic Python course. Students are not required to have any prior knowledge on programming or Python. This course will cover the basic concepts of programming up to Python specific modules and OOP design. It is available for all instructors now.
Learn about autograding Haskell coding assignments for Computer Science education courses. CodeGrade can help you use tools like input and output checking, Quickcheck, Tasty and HUnit unit test autograding, HLint code quality checking and code structure autograding using semgrep.