Programming Languages and Software Development

Programming Languages

At Stage Coding are Elaborated Source Programs

First-Generation Languages

Assemblers with a low level of abstraction.

Second-Generation Languages

No dependence on computer structure. Prime high-level languages. Examples include Fortran, Cobol, Algol, and Basic.

Third-Generation Languages

Seen under two approaches:

  1. Imperative Programming: Strongly typed languages with redundancy between declaration and use of each data type, facilitating verification during compilation. Examples include:
    • Pascal: Rigid data typing, separate compilation deficiencies.
    • Modula-2: Removes module specification from its concrete implementation, allowing secure compilation and hiding implementation details.
    • C: Flexible, without data restrictions.
    • Ada: Allows defining generic elements, concurrent programming with tasks, and their synchronization and cooperation.
  2. Object-Oriented, Functional, or Logical: Designed for specific fields. Examples include:
    • Smalltalk: Precursor of object-oriented languages.
    • C++: Combines C with classes, inheritance, and polymorphism.
    • Eiffel: Completely new object-oriented language, allows the definition of generic classes, multiple inheritance, and polymorphism.
    • Lisp: Precursor of functional languages, data organized into lists managed by functions, usually recursive.
    • Prolog: Representative of logic languages, used in expert systems.

Fourth-Generation Languages

Higher level of abstraction, usually not general purpose, not recommended for complex applications due to inefficient code generation. According to their practical application:

  • Databases: Users generate reports, listings, and summaries as needed.
  • Program Generators: Build fundamental abstract elements without descending into specifics needed in third-generation languages. Most generate Cobol management applications. Lately, CASE tools for object-oriented design have been developed, generating programs in C, C++, or Ada.
  • Calculating: Spreadsheets, simulation, and control design.
  • Others: Specification and formal verification tools, simulation languages, prototyping languages.

Features of the Language

Control Structures

Structured programming: sequence, general selection, case selection, repeat, counter loop, indefinite loop, definition of sub-functions and procedures (usually recursively defined), making code clearer and simpler.

Exception Handling

Errors or unusual events can occur due to:

  • Human Error: Entering data that causes a divide by zero.
  • Hardware Malfunctions: Device malfunction or limited resource capacity.
  • Software Errors: Software defects not detected in tests.
  • Input Data Gaps: Values out of range.

The corrective measure of the operating system is often aborting the program, but this is usually not acceptable. Instead, program execution is diverted to an appropriate predefined snippet. Ada has predefined treatments for some exceptions. Treatment analyzes the cause, logs it, and restores the system state. In Ada, execution is completely abandoned after treatment. In PL/1, execution resumes immediately after the exception point. When an exception occurs in a unit not scheduled for treatment, it propagates to the calling unit. This continues until it reaches the external drive and runs the default treatment if no other is defined. Concurrency is addressed differently in each language:

  • Coroutines: Tasks are coroutines similar to applets, passing execution control between them. Used in Modula-2.
  • Fork-Join: A task can start concurrent execution with a fork order. Competition ends with a join invoked by the same task, merging both tasks into one. Used in UNIX and PL/1.
  • COBEGIN-COEND: All concurrently running tasks are declared inside COBEGIN-COEND. Competition ends when all tasks finish. Used in Algol68.
  • Processes: Each task is declared as a process. All processes run concurrently from the program’s start. Used in Concurrent Pascal. In Ada, a process can be launched at any time.

Synchronization and cooperation between tasks are achieved through:

  • Shared Variables: Running on the same computer (multiprogramming) or different computers with shared memory (multiprocessing). Examples include semaphores, conditional critical regions, and monitors.
  • Message Passing: Running on computers without common memory (distributed processes) in a network enabling message passing. Examples include Communicating Sequential Processes (CSP), Remote Procedure Calls, and Ada Rendezvous.

Data Structures

Simple data: integer, real, enumerated, subrange type (limiting the range of an ordinal data type). Compound data: training, records, arrays, vectors, dynamic data (pointers). Constants: Named symbolic constants can be declared. In Modula-2, training or registry constants cannot be declared, unlike Ada. Type checking: Operations performed on data depend on the type verification level. There are five levels:

  • Level 0: (Without Types): Languages where new data types cannot be declared, such as Basic, Cobol, Lisp, and Apl.
  • Level 1: (Automatic Typing): The compiler decides the best type for each data, changing it if necessary.
  • Level 2: (Weak Typing): Automatic conversion, but only between similar types.
  • Level 3: (Rigid Typing): Data must be declared beforehand, but separate compilation allows for inconsistencies between declarations and usage in different modules. This is the case in Pascal.
  • Level 4: (Strong Typing): No escape route, the programmer must perform any type conversion. Type checking is done during compilation, loading, and execution. This is the case in Ada and Modula-2.

Abstractions and Objects

Functional Abstraction: In all languages, you define the name and interface of the abstraction, code the operation, and provide a mechanism for using it. Most languages hide the coding operation from users. Pascal, Modula-2, and Ada have block visibility, allowing them to view or modify data in outer blocks. Fortran has totally opaque blocks. Data types should group content (attributes of data abstraction) and operations for managing content, with a hiding mechanism preventing access by other means. Objects: Modula-2 lacks inheritance and polymorphism mechanisms, making it difficult for object-oriented design. Ada has overloading polymorphism but not cancellation or delay, which is also complicated. Suitable languages include Smalltalk, Object Pascal, Eiffel, Objective-C, and C++.

Modularity

The first required quality is separate compilation, allowing code to be compiled separately for each module. A complex module is safe at compile time if it can be checked that the use of an element is consistent with its definition. Fortran and Pascal have unsafe compilation. Ada and Modula-2 have safe compilation. In Modula-2, the interface is defined in the DEFINITION MODULE and encoded in the IMPLEMENTATION MODULE. In Ada, it’s the package and package body. In ANSI C, the compiler won’t compile if the interface module differs from the copy used in the program.

Language Selection Criteria

  • Customer Requirements: Application type. New languages are often associated with a specific scope. Assemblers might be used only for hardware-specific code parts.
  • Availability and Environment: Consider available compilers for the chosen computer. Development is easier with more powerful tools.
  • Previous Experience:
  • Reusability: Having available libraries and tools to organize them is beneficial.
  • Portability: Linked to the existence of a language standard that can be implemented in all compilers.
  • Using Multiple Languages: Not advisable, but sometimes easier to code in different languages.

Methodological Aspects

Coding Standards and Style

Establish rules specifying a coding style for the entire team, including: format and content of module headers, format and content for each type of comment, usage of names, and column alignment. Set restrictions such as maximum number of subroutines, maximum nesting of subroutines, and avoiding excessive arguments.

Error Handling

  • Default: Software errors.
  • Failure: A program element doesn’t work correctly, producing an erroneous result.
  • Error: An inadmissible program state that can be reached as a result of a failure.

Attitudes towards error handling include:

  • Not Considering Errors: Requires correct data input and a defect-free program.
  • Preventing Errors: Defensive programming, where the program systematically suspects entered data or arguments. Avoids incorrect results by not producing results in case of failure. Prevents error propagation, facilitating defect diagnosis.
  • Error Recovery: Restores the correct state and prevents error propagation. Involves two aspects:
    • Error Detection: Realizing that situations are wrong and performing appropriate checks.
    • Error Retrieval:
      • Forward Recovery: Identifies the error and takes actions to correct the program state, allowing execution to continue. This can be programmed through exceptions.
      • Backward Recovery: Corrects the program state to a state before the error. This is the approach in transaction-based systems, keeping data consistent. Systems with error recovery mechanisms are called fault-tolerant.

Aspects of Efficiency

With current computing power, clarity should not be sacrificed for efficiency. Memory Efficiency: Use a compiler with memory compression and efficient algorithms. Time Efficiency: Especially in real-time systems, adopt efficient algorithms. Sometimes, sacrificing memory efficiency and using coding techniques like tabular complex calculations, using macros, unfolding loops, pulling out code that doesn’t need to be repeated, and avoiding floating-point operations can improve time efficiency.

Software Portability

Factors include using standards and isolating specific code by dedicating a module for each.

Techniques for Unit Testing

For critical software, testing costs can be significant. Modules must undergo unit testing, integration testing, and full system testing.

Objectives of Software Testing

To make the program malfunction and discover defects with minimal effort, detecting the largest possible number of defects. Testing should be automated, requiring a test environment that restores predefined conditions at each pass. There are black-box and clear-box testing approaches.

Black-Box Testing

Testing based on the input-output specification of the software. The only strategy available to the client. Choose a comprehensive set of test cases. Methods to help develop test cases include:

  • Partition in Equivalence Classes: Determine classes and define tests for each.
  • Boundary Value Analysis: Focus on execution space areas close to the edges, where errors can be severe.
  • Version Comparison: Compare different program versions made by different programmers, subject them to the same tests, and evaluate the results. If they produce the same results, choose either version. This is not foolproof, as a specification error can affect all versions.
  • Using Intuition: People outside the module development team can bring a fresh perspective.

Clear-Box Testing

Takes into account the internal structure of the module. The program is made to transit through all possible execution paths, putting all code elements into play. If only black-box tests are performed, many paths remain unexplored. Black-box and clear-box testing should be complementary. Methods include:

  • Logical Coverage: Determine a set of basic paths that traverse the flowchart. The maximum number of paths is determined by the number of simple predicates: N paths = No. of predicates + 1. Different coverage levels exist:
    • Level 1: Test cases run all basic paths at least once, each separately.
    • Level 2: Test cases run all combinations of basic paths in pairs.
    • Level 3: Test cases produce a significant number of possible path combinations. Each module should ensure at least Level 1.
  • Loop Testing: Develop tests for:
    • Loops with an unbounded number of repetitions: Run the loop 0, 1, 2, several, and many times.
    • Loops with a maximum number (M) of repetitions: Run the loop 0, 1, 2, several, M-1, M, and M+1 times.
    • Nested Loops: Run all external loops the minimum number of times to test the innermost loop. For the next nesting level, execute external loops the minimum number of times and the inner loops a typical number of times. Repeat until all levels are tested.
    • Concatenated Loops: Test them separately using the previous criteria.
  • Using Intuition:

Estimation of Undetected Errors

It’s impossible to prove a module is faultless. To estimate undetected defects: record initial errors with the test case set, correct the module until it has no errors on the same set, introduce a specified number of errors into the module, subject the faulty version to the test cases, and count detected errors. Assuming the same proportion, the percentage of undetected errors is the same for initial and deliberate errors.

Integration Strategies

Address errors that occur when integrating modules. Several approaches exist:

  • Big Bang Integration: Integration is performed in one step.
  • Top-Down Integration: A main module is tested with scaffolding modules, which are replaced one by one with real modules, performing integration testing. Substitutes should be as simple as possible. The advantage is that the application’s possibilities are clear from the beginning. Drawbacks include limitations on parallel work and testing special situations.
  • Bottom-Up Integration: Lower-level modules are coded separately and in parallel. Test manager modules are written to make them work independently or in simple combinations. Advantages include easier parallel work and testing special situations. The drawback is that overall operation is difficult to test until the end of integration. The best solution is often a combination of bottom-up integration for lower-level modules and top-down integration for higher-level modules, called sandwich integration.

System Testing

Black-box testing of the entire system. Classes include:

  • Objective Tests:
    • Recovery: Check the system’s ability to recover from failures.
    • Security: Check protection mechanisms against unauthorized access.
    • Resistance: Check the system’s performance sensitivity to exceptional situations.
    • Sensitivity: Check the system’s handling of singularities related to algorithm performance.
    • Performance: Check system performance in time-critical aspects.
  • Alpha Tests: Performed in a controlled environment with support from the development team.
  • Beta Tests: Users work with the system in their normal environment without support, noting any problems.