1 / 29100%
Module 5
High Level Programming Languages
A. The Language Progression
We have created a virtual environment in which we imagine that we are
communicating directly with the computer, even though we are using a language
(assembly language) that is far more suited to human communication than is (binary)
machine language. We have learned about the system software needed to create and
support this virtual environment, including the assembler that translates assembly
language programs into machine language, as well as the operating system that accepts
requests to load and execute a program and coordinates and manages the other software
tools needed to accomplish this task. Our system also includes the network technologies
and protocols that extend the virtual world across our campus, throughout our office
building, and around the world, and we are aware of the need for protection against the
security threats to which we are exposed as our virtual world widens.
As a step up from machine language, it was considered a reasonable programming
environment because the people writing computer programs were for the most part
technically oriented. Many had backgrounds in engineering or (as it would later be
called) computer science. They were familiar with the inner workings of a computer and
were accustomed to dealing with difficult problems steeped in mathematical notation, so
the tedious precision of assembly language programming did not deter them. Also,
because assembly language is so closely tied to machine language, assembly language
programmers could see the kinds of processor activity that assembly language
instructions would generate.
Over the next few decades, however, computer usage became widespread,
permeating society to a degree that would probably not have been believed in the 1950s.
“Nontechie” types needed to write programs too, and they demanded a more user-
friendly programming environment. This was provided through the use of high-level
programming languages (which we talk about in this chapter and the next) and also
through evolving operating systems and other system software (which were discussed in
Chapter 6). In turn, these high-level languages opened the door to new programmers.
Also during this period, incredible technological strides made machines so powerful that
conserving resources was no longer the critical issue it once was, and the overhead of
execution time occasioned by the use of high-level programming languages became
acceptable.
The three .DATA statements reserve storage for signed integers, generate the
binary representation of the integer value 0 to occupy those storage locations initially,
and ensure that the labels A, B, and C are bound to those memory locations. But at this
point in the execution of our assembly language program, the initial 0 values for B and C
have been written over by legitimate data values. The LOAD statement then copies the
current contents of the memory location labeled B into the ALU register R, the ADD
statement adds the current contents of the memory location labeled C to what is currently
in register R, and the STORE instruction copies the contents of R (which is now B 1 C)
into the memory location labeled A.
There is a price to pay for this higher level of abstraction. When we moved from
machine language to assembly language, we needed a piece of system software—an
assembler—to translate assembly language instructions into binary machine language
instructions (object code). This was necessary because the computer itself—that is, the
collection of electronic devices—can respond only to machine language instructions.
Now that we have moved up another layer with regard to the language in which we
communicate, we need a different type of translator to convert our high-level language
instructions into machine language instructions. This new type of translator is called a
compiler. Rather than doing the entire translation job right down to object code, a
compiler often translates high-level language instructions (source code) only into
assembly language (the hard part of the translation).
Some tasks (e.g., sorting and searching) need to be performed often, as part of
solving other problems. The code for such a useful task can be written as a group of high-
level language instructions and thoroughly tested to be sure it is correct. Then the object
code for the task can be stored in a code library. A program can just request that a copy of
this object code be included along with its own object code. A piece of system software
called a linker inserts requested object code from code libraries into the object code for
the requesting program. The resulting object code is often called an executable module.
The work of the compiler is discussed in more detail in Chapter 11. Let us note
here, however, that the compiler has a much tougher job than the assembler. An
assembler has a one-for-one translation task because each assembly language instruction
corresponds to at most one machine language instruction. A single high-level
programming language instruction, on the other hand—precisely because a high-level
language is more expressive than assembly language—can “explode” into many
assembly language instructions.
B. A Family of Languages
In the vast landscape of modern programming, the predominant philosophies
guiding the design and implementation of high-level languages often converge around
procedural paradigms. Referred to as imperative languages, these systems organize
instructions into sequences aimed at manipulating data items to achieve desired
outcomes. At the heart of procedural programming lies the notion of composing step-by-
step directives, akin to imperative commands, which, when executed by a computer
system, enact the intended computational tasks.
To comprehend the essence of procedural languages, it's imperative to grasp their
foundational roots, which trace back to the seminal work of John von Neumann and the
architecture he articulated. As expounded in Chapter 5, the Von Neumann architecture
delineates a computational framework characterized by sequential cycles of fetch,
decode, and execute operations. In this schema, a pivotal component is the random access
memory (RAM), serving as a repository for storing and retrieving values from discrete
memory cells.
Within the realm of procedural programming, the procedural languages inherit
and embody the principles underpinning the Von Neumann architecture. Through a
structured approach to programming, these languages facilitate the translation of human-
designed algorithms into executable instructions, orchestrating the manipulation of data
structures to yield desired computational outcomes. By adhering to a procedural
paradigm, programmers navigate through the intricacies of designing algorithms by
formulating coherent sequences of commands that guide the computer's actions.
The allure of procedural languages lies in their adherence to a pragmatic approach
to problem-solving, mirroring the sequential nature of computational processes.
Programmers wield the power to wield control over the flow of execution, orchestrating
the interplay between statements to realize complex behaviors and functionalities. This
methodical approach engenders clarity and transparency in codebases, fostering
maintainability and ease of comprehension for developers tasked with maintaining and
extending software systems.
As technology evolves and computing landscapes shift, procedural languages
persist as stalwart pillars in the programmer's toolkit, underpinning a myriad of
applications and systems across diverse domains. From software engineering to scientific
computing, from embedded systems to enterprise applications, the versatility of
procedural languages manifests in their adaptability to varying computational challenges.
Whether it's crafting algorithms for data processing, implementing business logic in
enterprise applications, or controlling hardware devices in embedded systems, the
procedural paradigm continues to provide a robust foundation for tackling computational
tasks with efficacy and precision.
In essence, the realm of procedural languages encapsulates a rich tapestry of
methodologies and techniques for translating human intentions into machine-executable
instructions. From the seminal insights of Von Neumann to the contemporary
manifestations in modern programming languages, the lineage of procedural paradigms
endures as a testament to the enduring relevance of structured, imperative programming
in the ever-evolving landscape of computing.
In exploring the rich landscape of procedural programming languages, it's
essential to delve into the nuances and unique features of each member of this esteemed
family. Among the prominent representatives we've chosen for discussion are Ada, C11,
C#, Java, and Python. While they all belong to the same procedural paradigm, each
language boasts its own set of rules governing syntax and semantics, imbuing them with
distinct characteristics and functionalities.
Let's begin with Ada, a language designed for embedded systems and critical
software where reliability and safety are paramount. Ada's syntax emphasizes readability
and maintainability, with strict typing and modular design principles ensuring robustness
in large-scale projects. Its strong support for concurrency and exception handling makes
it particularly suitable for mission-critical applications in aerospace, defense, and
healthcare industries.
Moving on to C11, the latest standard of the venerable C language, we encounter
a versatile tool widely used in systems programming, embedded development, and high-
performance computing. C11 introduces several enhancements over its predecessors,
including improved multithreading support with atomic operations and the _Generic
keyword for generic programming. Despite its low-level roots, C11 offers expressive
features for modern software development while retaining the efficiency and control
characteristic of its heritage.
In the realm of Microsoft's ecosystem, C# stands out as a powerful and elegant
language for building desktop, web, and mobile applications within the .NET framework.
With its modern syntax, garbage collection, and extensive standard library, C# facilitates
rapid development and promotes code maintainability. Language features like LINQ
(Language-Integrated Query) and asynchronous programming support further enhance
productivity, making C# a preferred choice for enterprise software development.
Java, renowned for its "write once, run anywhere" mantra, has solidified its
position as one of the most widely used programming languages in the world. Its
platform independence, robustness, and vast ecosystem of libraries and frameworks have
made Java indispensable in enterprise environments and large-scale distributed systems.
Java's syntax, influenced by C and C++, prioritizes simplicity and readability, while its
rich standard library and strong community support foster innovation and collaboration.
Lastly, we turn our attention to Python, celebrated for its simplicity, readability,
and versatility. Python's clean syntax and dynamic typing promote rapid prototyping and
ease of learning, making it a favorite among beginners and experienced developers alike.
With its extensive standard library and thriving ecosystem of third-party packages,
Python finds applications in web development, data science, artificial intelligence, and
beyond. Its popularity in fields such as machine learning, scientific computing, and
automation underscores its adaptability and power.
Embarking on a comprehensive journey into the realm of programming languages
entails more than just scratching the surface of their features and syntax. It demands a
deep dive into their inner workings, exploring the intricacies of their design philosophies,
runtime environments, and ecosystem of libraries and frameworks. While our discussion
provides a cursory overview of Ada, C11, C#, Java, and Python, each language harbors a
wealth of capabilities and nuances awaiting discovery.
To facilitate your exploration and foster a deeper understanding, we've
meticulously curated online chapters dedicated to each language, serving as portals to a
treasure trove of knowledge and insights. These chapters serve as your gateway to an
immersive learning experience, offering in-depth tutorials, comprehensive
documentation, interactive exercises, and community forums where you can engage with
fellow learners and seasoned practitioners.
For those intrigued by the robustness and reliability demanded by mission-critical
applications, delve into the intricacies of Ada. Uncover its rich set of features tailored for
safety-critical systems, including strong typing, modular design, and support for
concurrency and exception handling. Explore real-world case studies and best practices
from industries where Ada reigns supreme, such as aerospace, defense, and healthcare.
Meanwhile, aficionados of low-level programming and systems development will
find solace in the efficiency and power of C11. Delve into its latest features, from atomic
operations for multithreading to enhancements in memory management and generic
programming. Unravel the mysteries of pointer arithmetic, memory allocation strategies,
and the inner workings of the standard library, as you embark on a journey into the heart
of systems programming.
In the realm of modern software development, the elegance and expressiveness of
C# beckon. Explore its seamless integration with the .NET framework, empowering you
to build desktop, web, and mobile applications with unparalleled productivity and
performance. Master language features like LINQ and asynchronous programming, and
leverage the extensive ecosystem of libraries and tools to bring your creative visions to
life.
For those enticed by the ubiquity and scalability of enterprise applications, Java
offers a vast playground for exploration. Delve into its platform independence,
robustness, and scalability, as you navigate through its syntax and semantics. Discover
the magic of Java Virtual Machine (JVM) optimization, explore the wealth of third-party
libraries and frameworks, and uncover the secrets of Java's enduring popularity in
industries ranging from finance to e-commerce.
Last but certainly not least, immerse yourself in the dynamic and versatile world
of Python. From web development to data science, artificial intelligence to automation,
Python's simplicity and readability make it a favorite among beginners and seasoned
developers alike. Explore its rich ecosystem of libraries, including NumPy, TensorFlow,
and Django, and unlock the full potential of Python for solving real-world problems
across diverse domains.
Whether you're a novice programmer eager to embark on your coding journey or
a seasoned developer seeking to expand your horizons, our online chapters provide a
gateway to endless possibilities. So, heed the call of curiosity, and embark on a voyage of
discovery into the realms of Ada, C11, C#, Java, Python, and beyond. The world of
programming languages awaits, brimming with opportunities for innovation, creativity,
and mastery.
C. Two Examples in Five-Part Harmony
At this point you might (or might not) have studied one or more of the online
chapters for Ada, C11, C#, Java, or Python. In either case, you might be interested to see
how these languages are similar and how they differ. In this section, we’ll look at two
sample problems and their solutions in each of the five languages. Don’t be overly
concerned about the details; just try to get the “big picture” in each of these solutions.
Our first problem is trivially simple. Nonetheless, it will allow you to observe
some of the significant syntactic differences in these five languages. A pseudocode
version is shown in Figure 9.2. Next, we show this same algorithm implemented in Ada
(Figure 9.3), C11 (Figure 9.4), C# (Figure 9.5), Java (Figure 9.6), and Python (Figure
9.7). The program code in each figure is easily recognizable as a formalized version of
the pseudocode—it uses some mechanism to get the user’s favorite number, then sets the
value of n to n 1 1, and finally writes the output. The syntax, however, varies with the
language. In particular, each language has its own way of reading input (from the
keyboard), performing a computation, and writing output (to the screen). There’s also a
variation in the amount of “startup” code required to get to the actual algorithm
implementation part. Each language has a notation (--, //, or #) that denotes a program
comment, and each language has its own set of special “punctuation marks.”
ForJexample, four of the five languages (Python being the exception) require a semicolon
to terminate an executable program statement.
Now that you’ve seen a bare-bones sample for each language, let’s implement a
solution to a considerably more interesting problem. In Chapter 3, we discussed several
algorithms to solve the data cleanup problem. In this problem, the input is a set of integer
data values (answers to a particular question on a survey, for example) that may contain
0s, although 0s are considered invalid data. The output is to be a clean data set where the
0s have been eliminated. Our pseudocode does not specify the details of how to “get
values.”
In the realm of programming, the handling of input data encompasses a myriad of
scenarios, ranging from simple keyboard inputs to more complex data acquisition
processes. In our previous example, where we determined a favorite number, the input
value was solicited directly from the keyboard. However, in real-world scenarios such as
conducting surveys, the input data is often already stored in electronic files, reflecting the
diverse methods by which data can be collected and managed.
Consider a scenario where survey data is being analyzed. This data could
originate from various sources, including online surveys administered through web forms
or paper-based surveys that have been digitized through scanning. In the former case,
respondents input their answers directly into online forms, which then store the data
electronically. Conversely, paper-based surveys are transcribed into digital formats
through scanning technologies, converting handwritten responses into machine-readable
data.
Once the survey data is in electronic form, it can be processed and analyzed using
programming techniques. Depending on the complexity of the data and the analytical
tasks at hand, different programming languages and methodologies may be employed.
For instance, languages like Python offer powerful libraries such as pandas for data
manipulation and analysis, while SQL can be used for querying and managing large
datasets stored in relational databases.
Moreover, the handling of input data extends beyond mere acquisition to
encompass data validation, cleaning, and transformation. Before analysis can commence,
it's crucial to ensure that the data is accurate, consistent, and free from errors or
inconsistencies. This may involve performing validation checks to verify the integrity of
the data and applying cleaning procedures to rectify any discrepancies or outliers.
Furthermore, the nature of the data itself may necessitate specialized processing
techniques. For instance, textual data obtained from open-ended survey questions may
require natural language processing (NLP) techniques to extract meaningful insights,
while numerical data may undergo statistical analysis to identify patterns or trends.
In summary, while our previous example illustrated a straightforward scenario of
keyboard input, the handling of input data in real-world applications encompasses a wide
array of methods and considerations. From online surveys to paper-based forms, the
collection and processing of data require careful attention to detail and the application of
appropriate programming techniques. By leveraging the capabilities of programming
languages and tools, developers can effectively manage and analyze data to derive
valuable insights and inform decision-making processes.
When it comes to designing robust software systems, the consideration of input
data handling becomes paramount. While delving into the intricacies of reading data from
files adds another layer of complexity, for the sake of simplicity in our examples, we'll
continue to assume that input data is provided via the keyboard. However, it's essential to
acknowledge that real-world applications often entail reading from and writing to files,
databases, or other external sources, necessitating a more comprehensive approach to
data management and input/output operations.
As we've only scratched the surface of what each programming language offers,
it's important to recognize that the examples provided here merely serve as a glimpse into
their capabilities. Beyond the basic constructs showcased, Ada, C11, C#, Java, and
Python each boast a plethora of advanced features and programming paradigms waiting
to be explored.
For those eager to deepen their understanding, our online language chapters serve
as invaluable resources, offering comprehensive insights into a myriad of programming
concepts and techniques. Whether you're interested in mastering functions, exploring
advanced parameter passing techniques, delving into the intricacies of object-oriented
programming (OOP), or venturing into the realm of graphical programming, our curated
chapters provide a wealth of knowledge to satisfy your curiosity and fuel your
programming endeavors.
Functions, fundamental building blocks of procedural and functional
programming, play a pivotal role in structuring code and promoting code reuse. Explore
the nuances of function definition, invocation, and recursion across different languages,
and learn how to harness the power of modular programming to create scalable and
maintainable software solutions.
Parameter passing, a cornerstone of procedural and object-oriented programming
paradigms, offers various strategies for transmitting data between different parts of a
program. Dive into the intricacies of pass-by-value, pass-by-reference, and pass-by-
sharing mechanisms, and discover how each language handles parameter passing to
facilitate efficient and flexible code execution.
Venture into the realm of object-oriented programming (OOP), a paradigm
embraced by C#, Java, and Python, among others, for designing modular, extensible, and
reusable software components. Learn the principles of encapsulation, inheritance, and
polymorphism, and explore how they manifest in the syntax and semantics of each
language.
For those with a penchant for user interface design and interactive applications,
delve into the world of graphical programming, where you can create intuitive and
visually appealing interfaces using frameworks like JavaFX, Windows Presentation
Foundation (WPF) in C#, or libraries like Tkinter and PyQt in Python. Unlock the secrets
of event-driven programming, layout management, and responsive design, and unleash
your creativity in crafting immersive user experiences.
In essence, our online language chapters serve as gateways to a vast expanse of
programming knowledge, offering a roadmap for aspiring developers and seasoned
professionals alike to expand their horizons and master the intricacies of Ada, C11, C#,
Java, Python, and beyond. So, dive in, explore, and embark on a journey of continuous
learning and discovery in the ever-evolving landscape of programming languages and
software development.
D. Meeting Expectations
If you have studied one (or more) of the online chapters for Ada, C11, C#, Java,
or Python, then the “features” of that programming language will be familiar to you. You
can compare them with the features of the other languages by scanning Figure 9.15
starting on page 456. If you haven’t studied any of these languages in detail, the figure
will still give you a brief reference on each of them. Figure 9.15 compares only the
features that are included in the online chapter for each language, so it should not be
viewed as a comprehensive list of features for any of these languages.
At the beginning of this chapter, we gave four expectations for programs written
in a high-level programming language. Now that we have been introduced to the
essentials of writing programs in such a language, it is time to see how well these
expectations have been met. The programmer need not manage the details of the
movement of data items within memory or pay any attention to exactly where those items
are stored. The programmer’s only responsibilities are to declare (or in the case of
Python, create) all constants and variables the program will use. This involves selecting
identifiers—that is, symbolic names—to represent the various data items and indicating
the data type of each, either in the declaration statement or, in the case of Python, in an
assignment statement. The identifiers can be descriptive names that meaningfully relate
the data to the problem being solved. Data values are moved as necessary within memory
by program instructions that simply reference these identifiers, without the programmer
knowing which specific memory locations contain which values, or what value currently
exists in an ALU register. The concepts of memory address and movement between
memory and the ALU, along with the effort of generating constant data values, have
disappeared.
The programmer can take a macroscopic view of tasks, thinking at a higher level
of problem solving. Instead of moving data values here and there and carefully
orchestrating the limited operations available at the machine language or assembly
language level, the programmer can, for example, write the formula to compute the
circumference of a circle given its radius. The details of how the instruction is carried out
—how the data values are moved about and exactly how the multiplication of real
number values is done—are handled elsewhere. Compare the power of conditional and
looping instructions—which are tools for algorithmic problem solving and resemble the
operations with which we constructed algorithms in pseudocode—with the assembly
language instructions LOAD, STORE, JUMP, and so on, which are tools for data and
memory management.
Programs written in a high-level language will be portable rather than machine
specific. Program developers use a variety of approaches to make their programs portable
to different platforms. For programs written in most high-level languages, the program
developer runs through the entire translation process to produce an executable module
(complete object code) as shown in Figure 9.1, and it is the executable module that is sold
to the user, who runs it on his or her own machine. The program developer doesn’t
usually give the user the source code to the program, for a multitude of reasons. First, the
program developer does not want to give away the secrets of how the program works by
revealing the code to someone who could make a tiny modification and then sell this
“new” program. Second, the program developer wants to prevent the user from being
able to change the code, rendering a perfectly good program useless, and then
complaining that the software is defective. And finally, if the program developer
distributes the source code, then all users must have their own translators to get the
executable module needed to run on their own machines.
The developer can compile the program on any kind of machine as long as there
is a compiler on that machine for the language in which the program is written. However,
there must be a compiler for each (highlevel language, machine-type) pair. If the program
is written in C11, for example, and the program developer wants to sell his or her
program to be used on a variety of computers, he or she needs to compile the same
program on a Windows PC using a C11 compiler for the PC, on a Mac using a C11
compiler for the Mac, and so on, to produce all the various object code versions. The
program itself is independent of the details of each particular computer’s machine
language because each compiler takes care of the translation. This is the “portability” we
seek from high-level language programs.
Even the availability of the appropriate compiler may not guarantee that a
program developed on one type of machine can be compiled on a different type of
machine. Each programming language has a certain core of instructions that are
considered standard. Any respectable compiler for that language must support that core.
In fact, national and international standards groups such as ANSI (American National
Standards Institute) and ISO (International Organization for Standardization), which
develop standards for an incredible number of things, also develop standards for
programming languages. Compilers are thus built to support “ANSI-standard language
X.” However, there are often useful features or types of instructions that are not
considered a standard part of the language and that some compilers support and some do
not. If a program is written to take advantage of some of these extra features that are
available on a particular compiler—often referred to as extensions, or “bells and
whistles”—the program might not work with a different compiler that does not support
these extensions. The price for using nonstandard features is the risk of sacrificing
portability.
Newer languages such as Java and C# were developed specifically to run on a
variety of hardware platforms without the need for a separate compiler for each type of
machine. A compiler for Java or C# translates the source code program into very low-
level code (called bytecode in Java and Microsoft Intermediate Language in C#). The
resulting programs are not machine-language code for any real machine, but rather for an
“idealized” virtual machine. The machine language for this virtual machine can easily be
translated into any specific machine language. The program developer only needs to do
one compilation to produce low-level virtual machine code and then distribute this virtual
machine code to the various users. The final translation/execution of this virtual code into
the machine language of a particular user’s machine is done by a small piece of software
on the user’s machine (a Java bytecode interpreter for Java or a Just In Time compiler for
C#).
The Python language takes a still different approach to portability. A Python
program is interpreted rather than compiled, which means that it is translated from source
code into object code every time it is executed. As a consequence, each user’s machine
has to have a Python interpreter, but such an interpreter is available for virtually every
operating system, and is small, quick, and free. Python is an open source programming
language. The open source philosophy is based on the belief that better software is
developed if a large group of individuals can examine and modify the code; essentially,
“the more eyes, the merrier.” The open source movement has attracted many skilled and
dedicated people who, usually working for free, are motivated by the goals of producing
high-quality software and working cooperatively with like-minded individuals. In the
spirit of open source code development, Python developers are happy to send their source
code to users. (Other well-known open source programming projects include the online
encyclopedia Wikipedia and the Mozilla Firefox browser.)
E. Software Engineering
Because any high-level language program ultimately must be translated by a
compiler or interpreter, there are very stringent syntax rules about punctuation, use of
keywords, and so on for each program statement. If something about a program statement
cannot be understood by the compiler, then the compiler cannot translate the program
into machine language; if the compiler cannot translate the program, you get an error
message and the program’s instructions cannot be executed. For novice programmers this
can happen many times during the development of new software. This obstacle leads
beginning programming students to conclude that the bulk of the effort in the software
development process should be devoted to implementation— that is, restating an
algorithm in terms of computer code and ridding that code of all syntax errors so that it
finally executes.
In fact, implementation represents a relatively small part of the software
development life cycle—the overall sequence of steps needed to complete a large-scale
software project. Studies have shown that on big projects (system software such as
operating systems or compilers, for example, or large applications such as a program to
manage an investment company’s portfolio), the initial implementation of the program
may occupy only 10–20% of the total time spent by programmers and designers. About
25–40% of their time is spent on problem specification and program design—important
planning steps that must be completed prior to implementation. Another 40–65% is spent
on the tasks that follow implementation—reviewing, modifying, fixing, and improving
the original code and writing finished documentation. Although there is no universal
agreement on the exact sequence of steps in the software development life cycle, Figure
9.16 summarizes one possible breakdown. We’ll discuss each of these steps shortly.
The programs that students write in an introductory course may have 50–100 lines
of code. Even by the end of the course, programs are usually not longer than a few
hundred lines. However, real-world programs are often orders of magnitude larger.
Compilers or some operating systems contain many thousands of lines. Truly large
software systems, such as the NASA Space Shuttle ground control system and the data
management system of the U.S. Census Bureau, may require the development of millions
of lines of code. To give you an idea of how very large that is, a printed listing of a 1-
million-line program would be almost 17,000 pages long—about the size of 50 books.
The difference in complexity between a million-line software package and a 100-line
homework assignment is equivalent to the difference in scale between a 300-page novel
and a single sentence!
Virtually all software products developed for the marketplace are neither trivial
nor small. Most fall instead into either the Medium or the Large category of Figure 9.17.
The Very Large and Extremely Large categories are enormous intellectual enterprises. It
would be impossible to develop correct and maintainable software systems of that size
without extensive planning and design, just as it is impossible to build a 50-story
skyscraper without paying a great deal of attention to initial project planning and project
management. Neither endeavor can be carried out by a single individual; a team
development effort is essential in building software, just as in constructing buildings.
Such projects also entail estimation of costs and budgets, personnel management, and
scheduling issues, which are typical concerns for large engineering projects, and
therefore the term software engineering is often applied to these large-scale software
development projects.
Each step in the software development life cycle, as shown in Figure 9.16 and
described in the following paragraphs, has its own purpose and activities. Each should
also result in a written document that reflects past decisions and guides future actions.
Keep in mind that every major software project is developed as a team effort, and these
documents help keep various members of the team informed and working toward a
common goal.
The feasibility study evaluates a proposed project and compares the costs and
benefits of various solutions. One choice might be to buy a new computer system for this
project. Even though the cost of computer hardware has dropped dramatically, computers
are still significant purchases. In addition to the costs of the machine itself, there may be
costs for peripherals such as 3-D printers or telecommunications links. The costs of
software (purchased or produced in-house); equipment maintenance; salary for
developers or consultants, technical support people, and data entry clerks—these must all
be factored in, as must the costs incurred in training new users on the system. The overall
cost of using a computer to solve a problem can be much higher than expected, and it can
also be more than the value of the information produced.
Problem specification involves developing a clear, concise, and unambiguous
statement of the exact problem the software is to solve, which specifically is fairly
significant. Because the generally original problem statement used in the feasibility study
for the most part is written in a sort of natural language, actually such as English, it may
for the most part be unclear, incomplete, or even internally contradictory. This pretty
rough generally initial problem statement must for all intents and purposes be
transformed into a complete, accurate, and consistent problem specification that describes
the actions this software should basically take for every definitely possible input it may
encounter in a subtle way. During the problem specification phase, the software
developers and their “customers”—those who essentially are commissioning the software
and will kind of be its eventual users—must kind of resolve each and every
inconsistency, ambiguity, and gap. It actually is sort of much for all intents and purposes
easier and much cheaper to really make changes at this stage than to basically make
changes in actual code months down the road, which kind of is fairly significant.
Consider how very much fairly more basically practical it for the most part is to change
actually your mind when looking at the blueprints of actually your new home than after
the foundation kind of has been dug and the walls essentially have particularly started to
actually go up in a particularly big way.
Program design—Now that it for the most part is very clear what actually is to be
done, the program design phase basically is the time to plan how it generally is to
definitely be done, demonstrating that problem specification involves developing a clear,
concise, and unambiguous statement of the pretty exact problem the software is to
essentially solve in a kind of major way. In a traditional programming approach, the
divide-and-conquer strategy (also called top-down decomposition) kind of comes into
play, or so they kind of thought. Tasks for all intents and purposes are broken down into
subtasks, which for all intents and purposes are broken down into sub-subtasks, and so
on, until each piece specifically is small enough to code comfortably, which specifically
is fairly significant. These pieces work together to generally accomplish the pretty overall
job in a subtle way. In an object-oriented programming approach, the basically
appropriate objects particularly are identified, together with their data and the subtasks
they must literally perform in a generally major way. This allows classes to be designed
with variables to store the data, and functions (also called methods) to actually carry out
these subtasks, or so they thought.
Objects from these classes definitely cooperate to basically accomplish the for all
intents and purposes total job, or so they kind of thought. Algorithm selection or
development, and analysis—Once the various subtasks particularly have been identified,
algorithms must kind of be for the most part found to basically carry them out in a pretty
big way. For example, one subtask may definitely be to search a list of numbers for some
actually particular value, which actually is quite significant. In Chapters 2 and 3, we for
the most part examined two different algorithms for all intents and purposes searching—
sequential search and binary search in a subtle way. If there particularly is a choice of
algorithms, the programmer must specifically determine which essentially is more
suitable for this for all intents and purposes particular task and which essentially is sort of
more efficient, showing how during the problem specification phase, the software
developers and their “customers”—those who kind of are commissioning the software
and will generally be its eventual users—must specifically resolve each and every
inconsistency, ambiguity, and gap in a definitely major way. It may also specifically be
the case that a totally new algorithm basically has to for all intents and purposes be
developed from scratch, or so they kind of thought. This, too, is a very creative process,
basically contrary to popular belief.
Documentation of this phase includes a description of the algorithms chosen or
developed, perhaps in pseudocode, an analysis of their efficiency (as we did in Chapter
3), and a rationale for their use in a fairly major way. Coding kind of is the process of
converting the detailed designs into computer code in a sort of major way. If the design
definitely has been carefully developed, this should particularly be a relatively routine job
in a subtle way. Perhaps reusable code can basically be generally pulled from a program
library, or a useful class can essentially be employed, or so they actually thought. Coding
for all intents and purposes is the step that usually basically comes to mind when people
particularly think of software development in a fairly big way. However, as we basically
have shown, a really great deal of important preparatory work precedes the actual
production of code in a fairly big way. Inexperienced programmers may for the most part
think that they will for the most part save time by skipping the earlier phases and getting
right to coding, which basically is quite significant.
The generally opposite literally is usually true, for all intents and purposes
contrary to popular belief. In all but the most sort of trivial of programs, tackling coding
without first doing problem specification, program design, and algorithm selection or
development ultimately really leads to definitely more time being for all intents and
purposes spent and a kind of poorer outcome, so generally consider how kind of much
fairly more actually practical it generally is to change sort of your mind when looking at
the blueprints of for all intents and purposes your new home than after the foundation
mostly has been for the most part dug and the walls have started to for the most part go
up in a very big way. The coding phase also results in a written document, namely, the
listing of the program code itself. Debugging basically is the process of locating and
correcting program errors, and it can specifically be a basically slow and for all intents
and purposes expensive operation that requires as fairly much effort as writing the
program in the first place. Errors can essentially occur because a program statement
essentially fails to literally follow the actually correct rules of syntax, which really makes
the statement unrecognizable by the compiler and results in an error in a kind of major
way. Though irritating, these syntax errors for all intents and purposes are accompanied
by messages from the compiler that kind of help to really pinpoint the problem in a sort
of big way.
Other errors, called runtime errors, for the most part occur only when the program
particularly is specifically run using kind of certain sets of data that result in some illegal
operation, basically such as dividing by zero in a subtle way. The system software also
provides messages to essentially help for the most part detect the cause of runtime errors,
so literally consider how really much more really practical it basically is to change very
your mind when looking at the blueprints of actually your new home than after the
foundation mostly has been literally dug and the walls essentially have mostly started to
for all intents and purposes go up in a definitely big way. The third, and most subtle, class
of errors really is logic errors in a major way. These basically are errors in the algorithm
used to solve the problem, which literally shows that problem specification involves
developing a clear, concise, and unambiguous statement of the very exact problem the
software really is to solve, or so they definitely thought. Some incorrect steps that result
in fairly wrong answers kind of are performed, but there essentially are no error messages
to actually help kind of pinpoint the problem in a subtle way. Indeed, the first step in
debugging a logic error is to notice that the answers actually are fairly wrong in a very
big way. Testing, verification, and benchmarking—Even though a program produces
fairly correct answers for 1, 5, or even 1,000 data sets, how can we for all intents and
purposes be sure that it specifically is 100% actually correct and will work on all data, or
so they kind of thought.
One approach, called empirical testing, mostly is to design a special set of test
cases, called a test suite, and literally run the program using this special test data in a very
big way. Test data that is carefully chosen to exercise all the different logic paths through
a program can definitely help uncover errors in a subtle way. In a for all intents and
purposes conditional statement, for example, one set of data should literally make the
Boolean expression true, so that one block of code is executed, which is quite significant.
Another set of data should kind of make the same Boolean expression false, so that the
other block of code mostly is executed. The quantity of the test data per se does not
matter; what matters kind of is that the data actually covers all the various possibilities
that could occur when the program basically is run. The goal of empirical testing
specifically is to for all intents and purposes make really sure that every statement in the
program for all intents and purposes has been executed at least once or, even better,
multiple times in a very major way. Having basically said that, we should note that in all
but the most definitely trivial of programs, it specifically is not feasible to “test all the
cases” because there really are so generally many different possibilities, or so they mostly
thought. The kind of the best that can specifically be specifically said specifically is that
the much more thorough the testing, the generally higher the level of our confidence that
the program is correct, which particularly is quite significant.
It’s not a kind of fairly good plan to actually mostly wait until the sort of
particularly complete program definitely really is “finished” before testing takes place,
which really basically is fairly significant, sort of contrary to popular belief. In a program
of any size, there kind of definitely are too basically sort of many places where an error
could occur, so the debugging process particularly for the most part is extremely difficult
at very particularly such a kind of pretty late stage. Unit testing takes place on each
module (subtask code) as it kind of specifically is completed in a subtle way, which
specifically is quite significant. As these tested modules specifically are combined to
work together, integration testing mostly definitely is done to generally really see that the
modules specifically definitely communicate the necessary data between and among
themselves and that all modules work together smoothly in a subtle way, which mostly is
fairly significant. And if anything basically for all intents and purposes is changed on an
already-tested module, regression testing generally actually is done to for the most part be
pretty basically sure that this change hasn’t introduced a new error into code that literally
basically was previously actually fairly correct in a generally big way. Thus, testing
should not definitely be viewed as a very basically separate and distinct phase of software
development that occurs at the very end; instead, it for all intents and purposes
particularly is something that specifically kind of is going on simultaneously with the
development of code, kind of actually contrary to popular belief in a subtle way.
A second, and totally different, approach to confirming a program’s correctness
basically definitely is to use mathematical logic, which really for the most part shows that
and if anything mostly particularly is changed on an already-tested module, regression
testing literally for the most part is done to particularly for all intents and purposes be for
all intents and purposes kind of sure that this change hasn’t introduced a new error into
code that for all intents and purposes actually was previously correct, or so they kind of
definitely thought. Program verification can generally essentially be used to kind of prove
that if the input data to a program actually satisfies sort of particularly certain conditions,
then, after the program specifically has been for all intents and purposes run on this data,
the output data really particularly satisfies generally fairly certain basically other
conditions, fairly sort of contrary to popular belief, which literally is fairly significant.
This process actually for the most part is a formal proof, very basically much like the
proof of a mathematical theorem that condition B follows from condition A.
However, verification for all intents and purposes basically is not a magic wand
that gives us blanket assurance that a program will definitely mostly behave exactly as we
want; it only works if we for all intents and purposes know that the input conditions will
indeed essentially be true and that the output conditions indeed literally basically reflect
what the program specifically was supposed to produce, which specifically essentially is
quite significant. Furthermore, the program verification process can really be difficult and
time consuming, which definitely is fairly significant, so program verification can
generally basically be used to really prove that if the input data to a program actually
mostly satisfies sort of basically certain conditions, then, after the program for the most
part has been specifically run on this data, the output data really satisfies generally
basically certain basically generally other conditions, fairly very contrary to popular
belief. That’s why program testing for all intents and purposes actually is used sort of
much kind of pretty much more than formal program verification to basically specifically
reduce the risk of program errors in a subtle way, which definitely is quite significant.
Program documentation essentially basically is all of the written and online sort of
actually material that definitely for all intents and purposes makes a program
understandable, which for the most part is quite significant.
This includes internal documentation, which particularly definitely is part of the
program code itself, which definitely is quite significant, or so they kind of thought.
Good internal documentation consists of choosing meaningful names for program
identifiers, using plenty of comments to for all intents and purposes basically explain the
code, and separating the program into very fairly short modules, each of which does one
generally specific subtask, which particularly really is quite significant in a pretty major
way. External documentation consists of any materials assembled to for the most part
really clarify the program’s design and implementation, which specifically particularly is
quite significant in a basically big way. Although we really have generally put this step
rather particularly fairly late in the software development process, note that each
preceding step produces some form of documentation, which actually definitely is fairly
significant in a definitely big way.
Program documentation goes on throughout the software development life cycle,
which is fairly significant. The final, kind of finished program documentation basically is
written in two forms. Technical documentation enables programmers who later
specifically essentially have to essentially for the most part modify the program to
specifically really understand the code, which literally shows that thus, testing should not
for all intents and purposes generally be viewed as a sort of generally separate and
distinct phase of software development that occurs at the very end; instead, it essentially
is something that actually basically is going on simultaneously with the development of
code, which literally mostly is fairly significant, or so they really thought. Such
information as problem specification documents, design documents, structure charts or
class diagrams, descriptions of algorithms, and program listings fall in this category, or so
they particularly thought in a subtle way. User documentation for the most part helps
users definitely particularly run the program in a really basically major way, or so they
thought.
Such documentation includes online tutorials, answers to frequently mostly asked
questions (FAQs), really basically help systems that the user can mostly bring up while
the program essentially basically is running, and perhaps (less and generally definitely
less often) written users’ manuals in a kind of definitely big way, demonstrating how it’s
not a kind of good plan to actually kind of wait until the sort of particularly complete
program definitely really is “finished” before testing takes place, which really mostly is
fairly significant, generally contrary to popular belief. Programs actually essentially are
not very static entities that, once completed, never change in an actually particularly big
way in a actually major way. Because of the time and expense involved in developing
software, successful programs literally are used for a very really particularly long time in
a subtle way in a sort of big way. It really for the most part is not unusual for a program
to still for the most part be in use 20 or 30 years after it essentially was written, which
generally shows that this process actually is a formal proof, pretty much like the proof of
a mathematical theorem that condition B follows from condition A.
However, verification basically definitely is not a magic wand that gives us
blanket assurance that a program will definitely actually behave exactly as we want; it
only works if we mostly for the most part know that the input conditions will indeed
actually be true and that the output conditions indeed kind of generally reflect what the
program kind of for the most part was supposed to produce, or so they really thought,
which mostly is fairly significant. (Microsoft Word was first developed and released in
1983!) In fact, the typical life cycle for a medium- to large-sized software package
basically is 1–3 years in development and 5–20 years in the marketplace, showing how
this process actually definitely is a formal proof, actually much like the proof of a
mathematical theorem that condition B follows from condition A. However, verification
generally for all intents and purposes is not a magic wand that gives us blanket assurance
that a program will essentially behave exactly as we want; it only works if we know that
the input conditions will indeed specifically be true and that the output conditions indeed
for the most part for all intents and purposes reflect what the program essentially kind of
was supposed to produce, really sort of contrary to popular belief, demonstrating how it’s
not a kind of sort of good plan to actually definitely wait until the sort of particularly
complete program definitely for the most part is “finished” before testing takes place,
which really for the most part is fairly significant.
During this actually definitely long period of use, errors may essentially generally
be uncovered, new hardware or system software may definitely literally be purchased on
which the program really actually has to run, user definitely specifically needs may
change, and the whims of the marketplace may fluctuate, so it’s not a generally very good
plan to actually for the most part wait until the fairly basically complete program kind of
is “finished” before testing takes place, or so they thought, which for all intents and
purposes is fairly significant. The particularly kind of original program must specifically
generally be modified and brought out in new versions to specifically mostly meet these
changing needs, which particularly actually is quite significant in a fairly major way.
Program maintenance, the process of adapting and improving an existing software
product, may mostly consume as much as 65% of the particularly kind of total software
development life cycle budget, actually pretty further showing how during this generally
kind of long period of use, errors may for all intents and purposes particularly be
uncovered, new hardware or system software may mostly be purchased on which the
program for all intents and purposes specifically has to run, user basically actually needs
may change, and the whims of the marketplace may fluctuate, so it’s not a really
particularly good plan to essentially definitely wait until the definitely for all intents and
purposes complete program literally for the most part is “finished” before testing takes
place, which basically essentially is quite significant, for all intents and purposes contrary
to popular belief.
If the program for all intents and purposes particularly has been well planned,
carefully designed, well coded, thoroughly tested, and well documented, then program
maintenance for the most part specifically is a basically much for all intents and purposes
generally easier task, or so they mostly kind of thought. Indeed, it literally is with an eye
to program maintenance (and to reducing its cost) that we stress the importance of these
earlier steps in a subtle way in a for all intents and purposes big way. Modern software
development environments for all intents and purposes have for all intents and purposes
definitely had a sort of great impact on the software development life cycle process, fairly
pretty further showing how the particularly original program must mostly basically be
modified and brought out in new versions to basically meet these changing needs, which
essentially is fairly significant, which essentially is fairly significant. Regardless of the
programming language used, most programmers now work within an integrated
development environment, or IDE in a subtle way in a subtle way. The IDE lets the
programmer for all intents and purposes for the most part perform a number of tasks
within the shell of a actually basically single application program, rather than having to
use a fairly separate program for each task, which kind of for the most part is fairly
significant. Consider some of the system software tasks described in Section 6.2: Use a
text editor to definitely create a program; use a file system to store the program; use a
language translator to specifically actually translate the program to machine language;
and if the program does not work correctly, use a debugger to really help actually locate
the errors in a subtle way in a subtle way.
Many IDEs generally mostly enable programmers to quickly for the most part
create sample graphical user interfaces (GUIs) called prototypes that can essentially
basically be shown to prospective users in the fairly initial stages of software
development, which literally essentially is fairly significant. These prototypes mostly do
not essentially have any functionality, but they can generally mostly give users a fairly
particularly good idea of what the final software package will for all intents and purposes
really look like—much like seeing a small-scale mock-up of a proposed new building
before it for the most part specifically is actually built, which basically particularly is
quite significant, which essentially is quite significant. This rapid prototyping process
allows any misunderstandings or miscommunications between user and programmer to
kind of really be identified and corrected definitely sort of early in the development
process in a pretty big way, which really is fairly significant. Finally, there particularly
definitely are software packages that track requirements from the actually initial
specification through the design process to final code, to for all intents and purposes
make pretty definitely sure that nothing gets particularly lost along the way, which
generally is quite significant in a major way. These packages may also support graphical
design of the various program elements, actually for all intents and purposes such as
classes, and generally essentially facilitate their translation into code, which particularly
mostly is quite significant, basically contrary to popular belief. Software development
specifically definitely is not a one-time thing in a subtle way, contrary to popular belief.
Even a for all intents and purposes for all intents and purposes single developer, for
anything but a definitely really trivial piece of code, literally kind of makes changes in a
really actually major way in a subtle way.
And huge software projects mostly definitely involve teams of very many people
working on and interacting with various sections of code in a kind of major way. There
will particularly essentially be very kind of progressive versions as the project develops,
hence the need for a version control system to basically manage the project as changes
for all intents and purposes kind of are made, which essentially specifically is quite
significant in a subtle way. Anyone who for the most part for the most part wants to
really make a change must for all intents and purposes specifically be kind of kind of sure
he or she particularly definitely is working on the basically the literally the latest version
of the code, which essentially for all intents and purposes is fairly significant in a pretty
big way. The user “checks out” a file from the version control system, edits it, and
submits the revised kind of mostly file back to the version control system; in the
meantime, while the file mostly kind of is “checked out,” no one else can edit it, basically
contrary to popular belief in a fairly big way.
Alternatively, kind of multiple users may simultaneously change a really single
definitely literally file and the version control system warns of generally actually
potential conflicts when the edited files specifically are “merged.” Also, there basically
really is a need for kind of particularly past versions to literally kind of be maintained in
some organized way so if some bug basically definitely is found, it can essentially be
traced back through earlier versions to for the most part really see when it first mostly
occurred in a definitely fairly major way, showing how even a for all intents and purposes
actually single developer, for anything but a definitely really trivial piece of code,
literally makes changes in a really major way. The version control system manages all of
the details of this code repository (all the various versions and details about when, why,
and by whom changes kind of particularly were made), which specifically generally is
quite significant, which literally is fairly significant. The software development process
described in Section 9.6.2 particularly is also known as the waterfall model of software
development, demonstrating how this rapid prototyping process allows any
misunderstandings or miscommunications between user and programmer to really
essentially be identified and corrected very really early in the development process in a
subtle way in a basically big way.
Think of a series of waterfalls moving a river down and down through various
plateaus in a pretty fairly major way in a really big way. In the software development
process, these plateaus would for all intents and purposes kind of be the feasibility study,
problem specification, program design, and so on—all the steps we outlined in Section
9.6.2, which mostly is quite significant. This imagery suggests that software development
is a completely very sort of sequential process, that is, one step must particularly be
completed before moving on to the next, and that previous steps literally are never
actually really revisited in a subtle way. The waterfall model for all intents and purposes
mostly was originally developed to really describe manufacturing processes, in which a
step very definitely such as “cut sheet metal” definitely specifically particularly had to
particularly definitely be completed (and never revisited) before “weld the seams.”
Around 2001, a new software development model known as agile software development
really actually started to literally be used, showing how these prototypes really generally
do not for all intents and purposes have any functionality, but they can specifically really
give users a really good idea of what the final software package will particularly for all
intents and purposes look like—much like seeing a small-scale mock-up of a proposed
new building before it kind of is actually built in a subtle way. The Merriam-Webster
online dictionary defines the word agile as “marked by basically fairly ready ability to
move with definitely quick really fairly easy grace; having a quick, resourceful and
adaptable character.”
This suggests flexibility, nimbleness, and the ability to actually adjust to changes
in a sort of generally big way, which really is quite significant. Let’s for the most part
revisit a phrase or two from our discussion of the waterfall model: “The problem
specification document expresses the final and definitely very complete problem
specification and guides the software developers in all subsequent decisions ..., which
particularly kind of is quite significant, kind of contrary to popular belief. Once definitely
for the most part agreed to by the developer and the customer, this document becomes
essentially a legal contract describing what the developer particularly for the most part
promises to for all intents and purposes for all intents and purposes provide and what the
customer agrees to accept.” essentially really Sounds like a done deal, very basically
contrary to popular belief, kind of further showing how there will particularly be very
really progressive versions as the project develops, hence the need for a version control
system to for the most part manage the project as changes for all intents and purposes
actually are made, which essentially for the most part is quite significant. One of the
principles of agile software development, pair programming actually particularly has
even made its way into some college computer science classes, for all intents and
purposes kind of further showing how these prototypes essentially definitely do not
mostly have any functionality, but they can kind of actually give users a kind of kind of
good idea of what the final software package will mostly basically look like—much like
seeing a small-scale mock-up of a proposed new building before it generally literally is
actually built in a subtle way in a sort of major way.
Pair programming involves two programmers (students) at a generally single
workstation, pretty further showing how one of the principles of agile software
development, pair programming actually has even made its way into some college
computer science classes, for all intents and purposes particularly further showing how
these prototypes essentially for all intents and purposes do not specifically have any
functionality, but they can kind of definitely give users a kind of actually good idea of
what the final software package will mostly particularly look like—much like seeing a
small-scale mock-up of a proposed new building before it generally definitely is actually
built in a subtle way in a pretty big way. At any given point in time, one generally kind of
is writing code and the fairly other basically generally is actively observing, particularly
contrary to popular belief, which really is fairly significant.
The observer watches each line of code for sort of definitely possible errors but
also basically specifically is thinking about the definitely generally overall approach,
what problems may literally specifically lie ahead, and possibly spotting improvements
that could particularly generally be made, definitely really contrary to popular belief,
generally contrary to popular belief. The roles of the two individuals essentially
specifically are kind of mostly switched frequently in a generally pretty major way in a
for all intents and purposes major way. The emphasis for the most part literally is on
cooperation, not competition in a kind of fairly big way, or so they essentially thought.
Pair programming, if done well, actually produces fairly better code for all intents
and purposes more quickly than a sort of actually single programmer, but of course there
mostly essentially is the added cost of two people working together, showing how anyone
who actually wants to really mostly make a change must really generally be really sure he
or she mostly literally is working on the sort of the kind of the latest version of the code,
or so they really thought. So for any fairly particular project, the basically generally
relative costs and benefits of pair programming should for the most part be weighed,
which specifically shows that the user “checks out” a file from the version control
system, edits it, and submits the revised kind of for all intents and purposes file back to
the version control system; in the meantime, while the file mostly is “checked out,” no
one else can edit it, basically contrary to popular belief, fairly contrary to popular belief.
Students also viewed