Welcome to week 8 of the Medical software class. In this week, we're going into the most concrete components of the medical software life cycle. We'll talk about coding and testing, and in many ways, this is the part of the life cycle that is most similar to any other kind of software engineering you may have had exposure to. So for those of you who have a lot of expertise in software engineering, this will be a fair amount of revision, although we'll try to highlight some things that are unique to the medical software process. The goal of this week's lectures, we're going to give you some advice on coding, especially in this medical software domain. We'll talk about source code management, we'll introduce software testing, then we'll discuss what a testing plan looks like. We have five segments for you, with the first two are going to focus on code. First of all, construction of programming, and then managing code and revision control systems. Then we'll do an introduction to software testing in the last two segments, first, we'll do an example of integration testing within the context of real-time fMRI, and then we'll do a final segment on verification and testing and thinking about how one goes about developing a testing plan for software. As before, we're following the textbook and this week's material based on Chapter 14 of the textbook. Segment 1, we're going to talk about software construction, which is another word for coding or programming that you may have come across. As a warning, this is not a programming class. We're not going to teach you here how you should write code. There are many classes out there, both on Coursera and on other sites and colleges that will teach you how to program. We'll mostly focus on the management of the process here. The same applies to our discussion of testing. We'll try to zoom in a little bit on issues that are unique to medical software. If we look at the software life cycle and this is the V model that we've been following over the past few weeks. We've been through use cases, we've been through system requirements, and last week we finished the software design part. This week, we're getting to the bottom part of the cycle, and we're going to talk about implementation and then start working our way up towards low-level testing and verification. This is the goal for this week, next week we'll take a detour to talk about probability theory and statistics, and in Week 10, we'll complete the discussion of the life cycle, we'll talk about validation, deployment, maintenance, and retirement. Before we jump in, let's look at some of the regulatory background, some comments from the QMS document from the International Medical Device Regulators Forum. This defines coding. This development activity transforms the requirements, architecture design, including interface definition, recognized coding practices, secure, and architecture patterns into software items and the integration of those software items into a SaMD. Basically, this is where we move from designs to an actual piece of software. The result is a software item/system/product that satisfies specified requirements, architecture, and design. This is the goal to create a concrete piece of software that is faithful to its design and its requirements. Let's look at a second quote from this document. Good development practice incorporates appropriate review activities, and it follows a defined implementation strategy. We're not just sitting at a computer and hiking here. Design changes resulting from the review activity or development activity should be adequately captured and communicated to ensure that other development and quality management system activities remain current. For as we code we find something that needs to be changed, we follow a proper process. We have a review, we discuss, we change the designs, so the code and the design remain in sync. The final piece of advice from the IMDRF is that use of automated tools and supporting infrastructure is important for managing configuration and having traceability. The more the process is scripted, the more your build process happens in one go, the better off you are. Let's offer a few comments on programming here. You really want to follow the standard best practices taught in most computer science courses. There's no substitute for that. This is important. The code should be modular, well-documented, and easy to read. You want to keep comments in a single language. This may sound a side comment here but in a multicultural world where we have people from different countries, from different backgrounds with different first languages, you almost don't have an agreement that all comments will be in English, or in German, or in French or whatever the common language of the company. The last thing we need is a programmer writing their own piece of code and putting their comments in the code, the variable names, the function names, in their own national language. I heard of one case of a programmer where everything was in Finnish. You can understand that this code was hard to read by others and when they left, the code almost died with them. We'd like to use defensive programming techniques. Do not assume that the previous module is behaving correctly. Assume that it was hot. That's a good model in many ways. Check all your inputs before you proceed to implementing anything. You like to use a shared repository and appropriate branches. We'll talk a lot more about source code repositories in the next segment. Finally, try to avoid using the latest and greatest feature of any programming language or any unnecessary complexity. Again, our topic is complex enough and our goal is not to show off how complicated our code can be. To illustrate this point, we have a quick video from Dr. Steve Pieper, who'll talk a little bit about avoiding unnecessary complexity. This person told me environment's crazy level of detail abstraction and didn't write down any hint about what it was supposed to be. Or they adopted some latest and greatest craze language that nobody else uses. Those are the kind of mistakes, you can talk about in retrospect, I don't know if you can anecdotally warn against them. but I guess if I were to pick one that I think it's got a clever phrase about it. But one of the things that you see a lot are domain-specific languages. But then a concrete example of a C++ templates can be domain-specific language where people abstract things up and look the line is from a thing called the programmer's Devil's Dictionary. You can find online, and I don't know who originally wrote it. But they said that a domain-specific languages is a language in which code is written in one language and error messages are generated in a different language. Anytime you find yourself in that sort of a situation where you wrote one line of C++ code and got 42 pages of error messages, then you know that there's some disconnect between the level of abstraction you're working out in the problem you are trying to solve. The take-home lesson from this video is, don't code to impress, code to generate functional safe software. A second issue that someone more unique to medical software will apply to other kinds of software is internationalization. You have to assume that your software would need to support multiple languages from the start. If you want your software to be using Canvas outside your native country, then you may have to support other languages. The solution to this, the programming techniques involved is that you abstract all string handling, the strings go into separate files. So messages like, Error, click Okay to proceed, all of those things go into separate files. This applies to both messages and for GUI elements and names on the buttons and things like that. You want to have the ability for your software to have some sort of language selection in the preferences, whether that's user accessible or configured by whoever's doing the installation. You'll have a second video clip here from Dr. Venkataraman, who will tell you about internationalization and his experience with it. That's when the biggest thing came into this. The first question that any doctors from Europe are going to ask is, does it support German? Does it support French? You need to have your code structured in certain ways. You need to develop your framework in such a way that you can extract all the different strings out of it. The biggest thing about English or the language is the string which stores the data. If you have the ability or if your platform or your development environment has the ability to extract these strings and list them out as a table, then it makes it easier for internationalization. What you would do is you would work with the translator, you would provide them either the GUI mock-up or you would provide them the list of words that need to be converted to a particular language. They will start working and converting all of those things. You would just take that file well into the software and then basically see how it converts, and then give it to the doctor for feedback to say, hey, do these words make sense? The other part that happens very often in medical software world, any high-quality software development is static code reviews. This is where we have experienced programmers review the code statically and look for logical flaws and poor structure. Lack of common short variable names, function names that make no difference, all of those good things. GitHub can be used as a code review tool, so the process would go along the following lines. You create a branch, you make changes, you submit a merge request, somebody reviews the code statically, including using automated tools. We can run the code checkers, and then eventually, after correction is done, the code is merged back into the main branch. We will talk a lot more about this process in the next segment when we discuss source code management. A unique aspect of medical software, that it can live for a long time. Many medical software packages are maintained for extended periods of time and the code is highly likely to be reviewed and modified by programmers that were not present at the time of the design. There's an extra burden to write very clear and easy-to-read code. You may need to learn to optimize for readability over compactness. Perhaps you may even want to allow for some reduction in efficiency if this improves the readability. This is a critical point. If you've ever, those of you who are old enough, tried to read code that you wrote 15 years ago, sometimes it doesn't make sense to you. It's very hard to read old code. Imagine if this code was written by somebody else. When you write code, you need to write it in such a way such that it's easy to maintain. That may mean that you take some simplifications in there. You don't use the most complex technique, you don't write the most compact piece of code. Maybe even use a slightly simpler procedure that's a little slower because deep down that code not only needs to be working now, but it needs to be easy to fix and maintain over the many years that your software would be in used. Potential 10,15, 20 years down the road. One more we should talk about is this concept of SOUPS. These are external libraries, these are the components we depend on. This should be listed in your software design document. If during the implementation we discover that we need to use another library, and this routine should be familiar to you by now, we need to stop coding, ask that the software design be revised to include this extra library or maybe not, we'll see what the whole review processes. Get the revision approved and then, and only then do you continue programming. The developer should not add dependencies on their own. Dependencies have a way of introducing additional constraints. You may think that that library will solve your problem, but that may constrain you to only operate on a particular type of hardware and maybe your software needs to work on something else. Dependencies should be handled with care, and this should be a high level decision, not something that somebody does as they write code. Then we return to risk management. We will always return to risk management. Things can go wrong of course, at the coding step. We may have a plan, but our plan will now encounter the real world. As the German general Helmuth von Moltke said, "No plan survives contact with the enemy," when it comes to programming, you could argue that the real-world, the program is almost the enemy, not just a programmer, but the actual process of coding. We have a great design. Everything looks good on paper, but now it has to be implemented. At that point we'll see how well our design holds up when it has to be put into operation. In the same way that military plans, and this is what Helmuth Moltke was talking about, look good until you start the campaign and then you realize that the other side will do things. Same thing applies to any other kind of plan. We have a great plan. We start coding, we may discover things that go wrong. Then we have to think about how to adapt and adjust to make progress in that domain. The final thing to remember here is an all code written by humans is prone to errors. We identify parts of the code that are highly likely to fail due to human error and we come up with mitigation strategies for controlling for the risk created by such errors. Let's talk a little bit more about that. What causes software to fail? We discussed that part in week 4 as part of risk management, but let's revisit it. Large size and large complexity of any particular item, lack of skill and/or experience of the developer, any external stress in the circumstances in which the code was created; we're close to a deadline, aspects of the code that have a history of failure, this type of thing never succeeds, then you should worry about it. Any issues with the testing of this part of the code, maybe this wasn't fully tested or it was tested by somebody less experienced. Those are the type of things that cause software to fail. How do we mitigate the risk due to implementation problems? What are the things we can do? First, you assign tasks based on skill and experience. Have the most experienced developers handle the most complex piece of code that just makes so much sense. You need to simplify that the size of the complexity of the items and the solution here is to create a more detailed design prior to coding for such items. If you remember from IC 6234, they talk about how software falls to classes, B and C needs more detailed design. That's a risk mitigation strategy. The same applies throughout. If you have a piece of code that looks too complicated, it may spend a little bit more time designing it. Break it into pieces, it will make the implementation a little easier. Having identified what can go wrong, let's think about strategies to mitigate the risk that comes from implementation problems. The first strategy and this a very simple one, you want to assign task based on skill and experience of your developers. You want to find your most experienced developer and assigned to them the most complicated tasks. You want to simplify that the size of the complexity of the items. Perhaps this may involve creating a more detailed design prior to coding for such items. Again, if you remember in our discussion of IC 6234, there was a discussion of the need for a more detailed design for software and classes B or C. That principle can be generalized. If something carries higher risk because it's complicated than a solution to that is to do a more detailed design. Think a little bit harder about what this piece of software should look like before you code it. It will save you trouble later. We want to ensure that the parts of the code that carries significant risks are tested thoroughly, ideally by your most experienced testers. Sometimes, and this is risk-management 101, we need to acknowledge that while the design may have been a good idea, it cannot be safely implemented. This is, if you remember the three components of risk management, A, change designed to eliminate functionality. B, take measures to simplify and to add safeguards and three, add informational warning messages. This thing is likely to fail. Sometimes it's that first one. It's a great idea on paper, we can't implement it safely so there's nothing we can do. We need to change the design to ensure that we don't get into problems. This concludes this introduction into coding. In the next segment, we'll pick up the discussion. We'll talk about revision control systems. Thank you.