Contemporary topics related to effective software development techniques. I will mostly blog about my experiences teaching Computer Sciences at the undergraduate level.
A question was raised on LinkedIn (you can follow me there: www.linkedin.com/pub/hector-fontanez/28/713/6/) that got me thinking and I decided to blog about it. I am going to start by defining what generalization means in Object-Oriented Programming (OOP) and Design (OOD). Generalization in OOD implies that a specialized (child) class is based on a general (parent) class. This, in turn, implies a "is-a" relationship.
When you analyze the object types and their relationships as depicted in the image above, from left to right, it goes from very general to more specific. You could say that an Animal "is a" more general form of Dog, of Canine, and even than Mammal. Likewise, a Canine "is a" more general form of Dog.
If you analyze it from right to left, the opposite happens: it goes from more specific to more general. Put in other terms, the type gets broadened. Therefore, you can still refer to dogs as canines, mammals, or even animals. The problem with generalization is that broadening the type opens the door to type-casting problems (I will not be covering Generics on this blog). For example, we already established that dogs are animals, but birds, fish, and frogs, are also animals; although not canines or even mammals.
Specialization (the opposite of generalization) is achieved in OOD and OOP through inheritance. When you "read" this relationship as depicted using UML class diagrams, you say that a subclass "is-a" type of the superclass. In our example, a Dog "is-a" Canine, and a Canine "is-a" Mammal, and a Mammal "is-an" Animal. You can go from the bottom class to the top class and say that a Dog "is-an" animal and that statement will still be correct. In the book Effective Java by Joshua Bloch (my favorite book on Java), he establishes to favor composition over inheritance. I have heard many people say that inheritance was a mistake. The fact that people abuse inheritance it does not make it a mistake. The example illustrated here is a perfect case for inheritance. In fact, Joshua put it brilliantly in his book: "inheritance is appropriate only in circumstances where the subclass really is a subtype of the superclass. In other words, a class B should extend a class A only if a is-a relationship exists between two classes." An this reality must hold true for all derived subtypes of the immediate child. For example, are all types of canines mammals? The answer to that question is yes. Therefore, it is OK for Canine to extend Mammal. The question then must be answer for Mammal as well: Are all mammals animals? The answer is also yes. By default, all canines are animals because we already determined that all canines are mammals.
The problem is that often inexperienced developers find certain attributes and behaviors in a class and extend the class to get access to these attributes without really thinking about the true relationship between a superclass and a subclass. Remember, just because an airplane flies and has wings it does not make it a bird (even though it is often referred as such by aviators). A plane IS NOT an animal. Likewise, a car and a house both have doors and windows, but they are not the same thing; one is a dwelling and the other one a vehicle. However, they both have some similar attributes which I am going to use to explain abstraction.
Abstraction (in OOD) is emphasis on an idea, or a concept, rather than broadening or generalizing a type. In my opinion, abstraction and generalization are not synonyms in OOD; although they are in the English lexicon. Let's examine my statement to see if my opinion holds true. I already establish that a House "is-a" form of Dwelling, and that a Car "is-a" form of Vehicle. I also stated that both have windows and doors (points of entry, maybe?). Doors and windows are opened, closed, locked, unlocked, etc. The point here is that these two totally incompatible data types (Car and House) do share some commonalities when it comes to these objects; doors and windows. So how do we include these similar behaviors in disparate classes such as Car and House? The answer is through the use of interfaces. In OOD, an interface is a structure that allows the enforcement of certain properties (or concepts) on an object. In this case, the concept common between Car and House is that both are "Lockable." In fact, a Safe is neither a dwelling or a vehicle, but it is also "Lockable."
In this context, "Lockable" is a concept, an idea. It is a certain property that is exhibited by certain types of objects that do not necessarily have to be directly related (by inheritance) in any way, shape, or form. Moreover, not all cars are necessarily lockable just because they have doors and windows. A golf cart could be lockable by other means. Because the concept of being locked or unlocked could (and most likely will) change between types, these methods will be undefined in the interface and it will be up to each implementing class what this concept of "lockable" means to each one. In other words, the methods in the interface will be abstract (undefined) and the implementing class has the responsibility to define this behavior.
Java and C# define this structure by using the keyword "interface." In C++, the concept of interface is realized by creating classes that contain nothing but pure virtual methods. In Java, a interface could look as simple as the one below:
public interface Lockable
{
public void lock();
public void unlock();
}
Implementing classes must define what actions need to take place. For example, locking a car might require a difference action or sequence of events than locking a golf cart.
There are another construct, not as abstracted as interfaces. There are cases when you have different behaviors of related objects. For example, certain domestic animals serve us as pets. You could say that a pet is an animal that has certain characteristics; a certain behavior that separates them from other, wilder animals of the same type. Using dogs for example, a house dog may present certain characteristics that separate them from wild (stray) dogs. Specifically, we often teach our pets to do tricks. That characteristics has nothing to do with being a Dog; but has everything to do with being a Pet. It is possible to make Pet an interface with a method "doTrick()." We could also make use of abstract classes to achieve the same. If we were to make Dog an abstract class, we could include an abstract method "doTrick()" and allow subclasses of Dog decide what that means for them.
public abstract class Dog
{
public final void bark()
{
System.out.println("woof!");
}
public abstract void doTrick();
}
Then, we could have sublcasses define what trick they'll perform:
public class MyDog extends Dog
{
@Override
public void doTrick()
{
System.out.println("Rolling over for a treat!");
}
}
My dog knows how to roll over. But your dog can perform a different trick:
public class YourDog extends Dog
{
@Override
public void doTrick()
{
System.out.println("I am sitting still!");
}
}
And some dogs just don't know any tricks, and this is OK...
public class DumbDog extends Dog
{
@Override
public void doTrick()
{
// Do nothing. Dumb dog doesn't know any tricks
}
}
A question was posted on LinkedIn which caused me to get out of my shell and post again. After all, I know all too well how frustrating it is to try to get something to work and not being able to find any meaningful examples on the web (YES... we have all grown fond of Google to find examples and get lazy researching topics, but that's life...)
I decided to post some simple examples on how to create MS Word documents from scratch. This is all based on my postings on LinkedIn on this very topic. To read the thread, you may go here.
First question: What is Apache POI? In simple terms, this is an free, open-source software (FOSS) that provide an application interface for Microsoft documents. This product was created by the Apache Foundation POI team. My examples are based on version 3.8. The latest version is 3.9 and there is a beta 3.10 available.
Second question: What kind of Microsoft documents? Simply, MS Office documents (i.e. Word, Excel, Power Point, Publisher, Visio, Outlook message, etc.)
My first posting (hopefully I will not get lazy and post other examples) in for Microsoft Word documents. And here it goes.... The first thing you should be aware of is that there are three different types of Microsoft Word Documents. Each of these types are realized by a POI class. These are:
XWPFDocument for Word 2007 and later
HWPFDocument for Word 97 - Word 2003
HWPFOldDocument for Word 95
I will be skipping over the Word 95 example. I really hope that there isn't someone out there with this particular need. But if you are out there and you run into this post, let me know and I will post an example just for you.... How about that!?
I will start with the easiest and probably the most relevant of all: Microsoft Word 2007 and later.
Going back to basics, if you need to create a blank Word document (or any blank file for that matter) all you need to do is create an output stream based on some file instance (i.e. "mydoc.doc") and you basically have you blank (Word) document:
OutputStream ostream = new FileOutputStream(new File("mydoc.docx"));
ostream.close();
File association is what determines what program is used to open your files. Files with ".doc" and ".docx" extensions are opened using MS Word by default. If the file extension is not associated correctly, it really doesn't matter what your program does to create the file; that system will not be able to open the file until you correct this problem.
Also, remember that output streams are used to write to and create files. Input streams assume the file exists. Otherwise, it will throw an exception (can't read from a file that does not exist).
The second part is to obtain the parts of the document (i.e. header, footer, paragraph, etc.) and add it to your existing document. For this, you are going to need the appropriate POI class (XWPFDocument or HWPFDocument), and the output stream you just created so you can write to that document.
XWPFDocument docx = new XWPFDocument();
This is where the differences start. If you use HWPFDocument, you cannot create an instance of your document unless you create an input stream. For now, I am assuming you need to create a new Word 2007 or later file; since it is easier to explain.
Now that you have an instance of a Word document object, all you have to do is to set the parts. This is where familiarization with the API is necessary. For now, I am going to create a simple paragraph with "Hello World!" in it. To create paragraphs, you need to create two objects: the Paragraph container to hold the text and a character Run object to set the text and all text properties such as Font Family, size, color, etc.
To create a paragraph, use the document object to obtain an instance of paragraph as follows:
XWPFParagraph paragraph = docx.createParagraph();
Make sure you use the correct paragraph class for the document type you are using. Typically these classes start with 'X' for all of the Office 2007 and later document types. Once you obtain an instance of paragraph, you use the paragraph object to obtain an instance of character Run:
XWPFRun charRun = paragraph.createRun();
Lastly, you have to set the text, write it to the document, and close the stream:
The close() method flushes (saves) the stream and closes it. Therefore, it is redundant to call the flush() method before close(). You want to use flush() only if you want to save the file, but keep the stream open for further write operations.
This is about the simplest example on how to create a Word document from scratch. In my case, the document is created using Calibri font family with a font size of 11 and left-justified.
All together...
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
/**
* Creates Word 2007 and later documents
* @author Hector Fontanez
*
*/
public class DocxCreator
{
public static void main(String[] args)
{
File file = new File("mydoc.docx");
XWPFDocument docx;
OutputStream ostream;
try
{
docx = new XWPFDocument();
XWPFParagraph paragraph = docx.createParagraph();
XWPFRun charRun = paragraph.createRun();
charRun.setBold(true);
charRun.setFontFamily("Consolas");
charRun.setFontSize(16);
charRun.setText("Hello World!");
ostream = new FileOutputStream(file);
docx.write(ostream);
ostream.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
If you need to create Word documents using HWPFDocument, there are a few things you have to be aware of. The most important, the HWPFDocument constructor requires a POIFSFileSystem object, a DirectoryNode object, or an InputStream object. There is no no-arg constructor for this class like for XWPFDocument. Furthermore, this class does not have ability to create a Word document from scratch. Therefore, you must start with a blank document ".doc" or document template ".dot" file. The easiest is to create a blank document file and add it to your JAR. The following steps create a blank Word document from an existing template file (the only way you can, as silly as it sounds):
InputStream istream = new FileInputStream(new File("template.doc"));
HWPFDocument doc = new HWPFDocument(istream);
istream.close(); // This stream is no longer needed after doc is created
OutputStream ostream = new FileOutputStream(new File("mydoc.doc"));
doc.write(ostream);
ostream.close();
Now is a matter of obtaining the document parts and adding text to the document. I personally do not know how to change the font family because there is no getter method for this property. So, this example will show you how to set some of the properties, but not font family since I do not know how to. You will have to research this more in detail if this is important to you.
The process is similar as before, with the exception you need to use a new class to create instance of Paragraph: Range. To create a Range object, use the document object to obtain the instance:
Range range = doc.getRange();
Create a Paragraph instance using the Range object just created:
Since this is an empty document, there are no paragraphs. If you use anything other than zero at this point, it will throw an index out of bounds exception. Once you have paragraphs in the document, you can insert before or after any existing paragraph index.
Lastly, you create a CharacterRun instance using the paragraph object and set all the properties (text, text size, color, etc). An interesting point is that to set the font size, the passed argument is in half points. Therefore, to set a font size of 16, you must pass a value of 32 (twice the half point size). The following few lines set these properties. Remember to do this before calling the document's write method:
This example should create a Word 97-2003 with a bold, 16-point phrase "Hello World!" in the first (and only) paragraph using the default font family. I have never tried it, but I am pretty sure that creating Word 95 documents (HWPFOldDocument) is not much different than this. This is what it looks like when put all together:
I hope this blog has been of some help to someone out there. Again, if you want an example for Word 95, let me know by adding a comment to the blog. Also, comments about this topic or what you will like to see in the future, are always welcome.
This is my very first attempt at blogging. I hope I am not too bad.
For my first blog ever, I am choosing a topic that I believe is very important for all students and not just those majoring in Computer Sciences, Software Engineering, etc. Even entry-level professionals might benefit from the contents of this blog. At least, that's what I hope. That said, the example I am using in my blog was the result of a practical exercise I gave my Computer Sciences students a few days ago.
I have been teaching part time since 2008. Since I started teaching, I am noticing a trend that alarms me a great deal. It seems to me that students nowadays either lack effective problem solving skills or completely undermine the importance of effective problem solving. In fact, I have even notice this trend on exceptional students.
I hope that my effort in shedding some light in the subject is futile. Therefore, I ask of you to leave feedback, positive or negative, once you finish reading this blog.
I assigned my students a problem from Y. Daniel Liang's book, Introduction to Java™ Programming (8th Edition), in which they had to generate a software solution to display a sequence of numbers in the following matter:
On the surface, this problem seems simple; especially for the seasoned professional. However, often we rushed to judgement and start generating solutions without really understanding the problem. The number one rule of problem solving is to seek to understand the problem first. This seems pointless to mention, but experience has shown me that the reason why dedicated students fail to come up with effective solutions to problems is because they started on this task without gaining a true understanding of the problem at hand.
One technique to effectively understand problems is called "Problem Decomposition." This is a process where a big problem is broken down into smaller, more manageable problems. The idea behind Problem Decomposition is that once you truly understand each individual sub-problem and generate effective solutions for each one independently, integrating the solutions should solve the bigger problem. The best text I have found on this topic is Software Engineering Theory and Practice by Dr. Shari Lawrence Pfleeger. Going back to the topic at hand, I will illustrate how this technique helps in generating an effective solution to the problem illustrated in Figure 1.
Initially, I broke down the problem into two parts: 1) Displaying the right numeric sequence, and 2) Aligning the numbers correctly (padding). Because padding is nothing more that printing out spaces, I decided to leave that problem for last and decided to solve the numeric sequencing part of the problem first. Since I decided to solve the numeric sequencing sub-problem, I decided transform the output shown in Figure 1 to fit my numeric sequencing sub-problem (without padding). The result is shown in Figure 2 below:
Having removed the padding from the original output allowed me to see this problem in a different light. However, this problem can be further decomposed. I can see that there are two numeric sequences:
1) Numeric sequence in ascending order, and
2) Numeric sequence in descending order. Since the ascending order sequence is displayed first, I will once again transform my output to include only this sub-problem. The result is shown in Figure 3 below:
The level of problem decomposition I have achieved now allows me to see this part of the problem much easier than before. I can see now that, in order to display this numeric sequence, I need to implement some kind of nested loop. The outer loop controls my rows and my inner loop controls the columns. A structure such as the one below should provide the control I need to create new rows.
for(int row = 1; row <=8; row++)
{
// TODO inner loop goes here
System.out.println();
}
Now, I am ready to determine what my inner loop should do. I know that the number of digits displayed is related to the number of rows (row 1 had one number, row 2 has two numbers, and so on). I also see that the sequence is powers of two, where the index of the column equals the exponent value. For example, in every row, the number in the first column (column index 0) is 1, or 20. Decomposing the original problem down to this level has allowed me to easily determine that an effective solution to this sub-problem, can be something like the code snippet below:
for(int row = 1; row <=8; row++)
{
for(int column = 0; column <=row; column++)
{
System.out.print((int)Math.pow(2, column)); //I am ignoring the padding for now
}
System.out.println();
}
By analyzing the code above, you can see that I have addressed the two main points I discovered:
1) The sequence displayed is powers of two. For this, I used the Java Math.pow method. This method takes two arguments; base and exponent. The base is just a literal constant and the exponent is the current column index location, just as I determined during my problem analysis.
2) I am using the current row's index value to determine how many iterations I am going to have for each row. This is because I established a relationship between the number of columns displayed in each row and the row number. I am also sticking to my plan not to worry about number alignment or padding until it is time to address that part of the problem.
Until now, everything had gone smoothly. But as it is often the case, our first solution might not be correct, or as effective as we had hoped. Once I execute the code above, I obtained the output shown below in Figure 4:
12
124
1248
124816
12481632
1248163264
1248163264128
FIGURE 4
I am missing the first row. This is an indication that my initial index location is off. I can now refine my code much easier since I only have one simple problem to solve, rather than a bunch of integrated sub-problems. Initializing my row counter variable with zero instead of one, and changing my row limit to seven instead of eight does the trick. However, my goal is to create a solution that looks like Figure 3 and not like Figure 4. At this point, I decided to add temporary padding to align the numbers so that my output looks like what I expect to see.
for(int row = 0; row <=7; row++)
{
for(int column = 0; column <=row; column++)
{
System.out.print((int)Math.pow(2, column)); //This is not the final padding
}
System.out.println();
}
Changing my solution to start at index 0 instead of 1 is a code refinement made so that the solution for this sub-problem displays exactly as expected. Now, my output looks exactly like Figure 3, and the sub-problem regarding displaying a numeric sequence in ascending order is solved! I can now attempt to solve the next sub-problem which is displaying a identical numeric sequence, in the same row, in descending order. How can this be done? Simple. Because the descending order sequence starts immediately after the ascending order concludes AND because it is displayed in the same row, it means that I need another looping mechanism following the one I created to solve the first sub-problem. In fact, because they are so similar, I could use a similar structure with slight modifications and I should be able to solve this particular sub-problem.
Analyzing Figure 2, I realized that the descending numeric sequence does not start with the largest number obtained from the ascending order sequence. For example, the third row's values are 1 2 4 2 1. This means that the initial value of the descending order sequence is one less than the current row index value. Because my sequence is descending this means that my counter variable for that loop must also be decreasing at the same rate. Lastly, the number being displayed is still a power of 2 relative to the current column index location. For this part of the problem, the columns are arranged in descending order. With that, I came up with the solution shown below:
for(int row = 0; row <=7; row++)
{
// Display ascending sequence
for(int column = 0; column <=row; column++)
{
System.out.print((int)Math.pow(2, column)); //This is not the final padding
}
// Display descending sequence
for(int column = row - 1; column>=0; column--)
{
System.out.print( (int)Math.pow(2, column) + " " ); //This is not the final padding
}
System.out.println();
}
The descending sequence code displays an output basically identical to the one shown in Figure 2. Therefore, another sub-problem has been solved! All there is left to be solved is the padding sub-problem. Once this is resolved, the entire solution should be solved.
I want to emphasize that, before I integrated the descending numeric sequence code with the ascending numeric sequence, I tried it in isolation by commenting my previous code out. Once I was satisfied with my solution to that sub-problem, I integrated it to my previous solution without any issues. That said, for problems more complex than my example, it is often the case that further refinement, tuning, etc must take place in order to integrate solutions in order for them to work. I want to make that point clear. Nevertheless, the issue that cannot be ignored is the fact that Problem Decomposition allows us to generate solutions much faster and much more effective than pure "Brute Force." Understand that I am not totally dismissing Brute Force as a technique for developing software. Rapid prototyping might be one of those cases where Brute Force might be the way to go. But, the topic of this blog is to get in the habit of using other techniques to effectively solve problems. I really hope that by now, you agree with me; even if it is up to a certain extent.
The last part of the problem is to come up with a padding scheme that will make my output look like the output illustrated in Figure 1. Once I started analyzing the alignment, I noticed that the number are right justified. When looking at the middle "column" of Figure 1, we see the following:
1 2 4 8 16 32 64 128
Common sense tells me that in order to align the top "1" with the "128", I must pad the "1" with two leading spaces. In fact, this is true for any single-digit number. Aligning two-digit number only require a single leading space. However, I must also pad the "128" in order to get some separation between it and the previous number (64). I determined that two leading spaces should be sufficient. Therefore, I can derive the following logic from my decision: If a number is three-digits in size, pad it with two leading spaces. Otherwise, if the number is two-digits in size, pad it with three leading spaces. Lastly, if the number is a single digit, pad it with four leading spaces. However, what of the "columns" with no numbers? The logic above allows me to determine that any given "column" is composed of 5 positions as shown below:
A column containing a one digit number: 4 spaces + number
A column containing a two digit number: 3 spaces + number
A column containing a three digit number: 2 spaces + number
This forces me to refine my code once again. I can see that wherever I am resolving for a number to be displayed, I must pad first, then display the value. Previously, I was doing the opposite. The result of my code refinement looks like the code snippet below:
for(int row = 0; row <=7; row++)
{
// Display ascending sequence
for(int column = 0; column <=row; column++)
{
int number = (int)Math.pow(2, column);
if( number >= 100 ) // Number is 3 digits
{
System.out.print(" " + number); // 2 spaces + number
}
else if ( number >= 10 ) // Number is 2 digits
{
System.out.print(" " + number); // 3 spaces + number
}
else // number is 1 digit
{
System.out.print(" " + number); // 4 spaces + number
}
}
// Display descending sequence
for(int column = row - 1; column>=0; column--)
{
int number = (int)Math.pow(2, column);
if( number >= 100 ) // Number is 3 digits
{
System.out.print(" " + number); // 2 spaces + number
}
else if ( number >= 10 ) // Number is 2 digits
{
System.out.print(" " + number); // 3 spaces + number
}
else // number is 1 digit
{
System.out.print(" " + number); // 4 spaces + number
}
}
System.out.println();
}
It is worth pointing out that the solutions inside each of the inner for loops are not the most effective, but it works well for this problem since we are bound to display powers of two from 1 to 128 (20 to 27). In order to perfectly align digits, we must pad empty columns with spaces which result in the same column width as columns with numbers. We already determined that the column width is 5. Therefore, "empty" columns must be padded with 5 spaces. And, these columns precede any numeric columns; which means another loop structure before we display any numbers. Also, the number of empty columns is inversely proportional to the number of columns containing numbers. For example, the first row has one numeric column and many empty columns. As we move to the next row, we notice that the number of empty columns have decreased by the same number of new numeric columns added. Therefore, a structure such as the one below should provide the necessary padding for empty colums:
Because this "default padding" must precede insertion of any numeric column, I must add the previous code snippet as the first process inside my row controlling looping structure as shown below:
for(int row = 0; row <=7; row++)
{
// Default padding
for(int column = 1; column<= 7 - row; column++)
{
System.out.print(" "); // Pad empty columns with 5 spaces
}
// Display ascending sequence
for(int column = 0; column <=row; column++)
{
int number = (int)Math.pow(2, column);
if( number >= 100 ) // Number is 3 digits
{
System.out.print(" " + number); // 2 spaces + number
}
else if ( number >= 10 ) // Number is 2 digits
{
System.out.print(" " + number); // 3 spaces + number
}
else // number is 1 digit
{
System.out.print(" " + number); // 4 spaces + number
}
}
// Display descending sequence
for(int column = row - 1; column>=0; column--)
{
int number = (int)Math.pow(2, column);
if( number >= 100 ) // Number is 3 digits
{
System.out.print(" " + number); // 2 spaces + number
}
else if ( number >= 10 ) // Number is 2 digits
{
System.out.print(" " + number); // 3 spaces + number
}
else // number is 1 digit
{
System.out.print(" " + number); // 4 spaces + number
}
}
System.out.println();
}
At this point, our problem is solved! Executing the code snippet above should display an output similar to the one illustrated in Figure 1. But, there is one more step that should be taken to make this solution more elegant are more readable. The code inside the numeric sequence loops is identical. Therefore, it should be extracted out into a method.
public class Problem4_19
{
public static void main(String[] args)
{
for (int row = 0; row <= 7; row++)
{
// Default padding
for (int column = 1; column <= 7 - row; column++)
{
System.out.print(" "); // Pad empty columns with 5 spaces
}
// Display ascending sequence
for (int column = 0; column <= row; column++)
{
alignNumber(column);
}
// Display descending sequence
for (int column = row - 1; column >= 0; column--)
{
alignNumber(column);
}
System.out.println();
}
}
private static void alignNumber(int column)
{
int number = (int) Math.pow(2, column);
if (number >= 100) // Number is 3 digits
{
System.out.print(" " + number); // 2 spaces + number
}
else if (number >= 10) // Number is 2 digits
{
System.out.print(" " + number); // 3 spaces + number
}
else
// number is 1 digit
{
System.out.print(" " + number); // 4 spaces + number
}
}
}
Executing the code above results in an output exactly as the one shown in Figure 1. I believe I have effectively demonstrated the benefits of using Problem Decomposition to solve complex problems (even though some of you think this problem was too easy). I have to go back again to my experience teaching, and even my experience as an experienced professional. I often see people jumping into coding without fully understanding the problem that needs to be solved. The overwhelming majority of the time, this results in time and money wasted, and, in some cases, this also results in a solution that partially works; which can be extremely dangerous for safety-critical systems.
This concludes my first blog. I hope I was able to explain Problem Decomposition in a manner that was both systematic and easy to follow. Please, leave me some feedback if you found this useful or not. I wish to do this more often and I need your help to get better at it.