Programming Languages and Design Principles

In the following, we will discuss the development of programming languages as a means to improve their ability to capture the software design at ever increasing abstraction levels. Or, from another point of view, we discuss why advances in programming language technology are driven by the need to make programming languages capable of capturing higher-level designs.

Programming Languages and Design Principles

Making Code Look Like Design

“Designing” with Pseudo-Assembler

“Designing” with Pseudo-Assembler

What does the following program do?

i = 1
TEST:	if i < 4
	then goto BODY
	else goto END
BODY:	print i
	i = i + 1
	goto TEST
END:

“Designing” with Pseudo-Assembler

What does the following program do?

i = 1
LOOP:	print i
	i = i + 1
	if i < 4 goto LOOP
END:	

Though both programs just print out "123" the second one is easier to read and comprehend. It has a better style:

Hence, the second variant, though functionally identical, is easier to understand, debug, change.

But:

Style can only be recommended, not enforced!

Designing with Structured Programming Languages

Designing with Structured Programming Languages

i = 1
while ( i < 4 ) {
	print(i)
	i = i + 1
}

Style gets enforced!

In the 1960th programming language support for better structuring of code emerged. Gotos were replaced by loops (while) and conditionals (if/else). Furthermore, procedures were introduced to support user-defined abstractions.

New words, new grammars, new abstractions enable developers to directly express looping/conditional computations, instead of emulating them by jumps. Using a – by then – modern structured programming language, it was no longer possible to write crossing gotos!

Better languages, More challenging tasks…

A simple image browser with structured programming: Image Browser

Code for Image Browser Structured into Procedures

main () {
draw_label(“Art Browser”)
   m = radio_menu(
      {“Whale”, “Eagle”, 
       “Dogfish”})
   q = button_menu({“Quit”})
   while ( !check_buttons(q) ) {
      n = check_buttons(m)
      draw_image(n)
   }
}
  
set_x (x) {
   current_x = x
}
  
draw_circle (x, y, r) {
   %%primitive_oval(x, y, 1, r)
}
  
set_y (y) {
   current_y = y
}
  
radio_menu(labels) {
   i = 0
   while (i < labels.size) {
      radio_button(i)
      draw_label(labels[i])
      set_y(get_y()
         + RADIO_BUTTON_H)
      i++
   }
}
  
radio_button (n) {
   draw_circle(get_x(),
      get_y(), 3)
}
  
get_x () {
   return current_x
}
  
get_y () {
   return current_y
}
  
draw_image (img) {
   w = img.width
   h = img.height
   do (r = 0; r < h; r++)
      do (c = 0; c < w; c++)
         WINDOW[r][c] = img[r][c]
}
  
button_menu(labels) {
   i = 0
   while (i < labels.size) {
      draw_label(labels[i])
      set_y(get_y()
         + BUTTON_H)
      i++
   }
}
  
draw_label (string) {
   w = calculate_width(string)
   print(string, WINDOW_PORT)
   set_x(get_x() + w)
}
  

In this case, the code is structured, but the procedures are not! It is hard, if not nearly impossible, to maintain or even extend this code.

Structured Programming with Style

main()
	
gui_radio_button(n)
	
gui_button_menu(labels)
	
gui_radio_menu(labels)
	
graphic_draw_image (img)
	
graphic_draw_circle (x, y, r)
	
graphic_draw_label (string)
	
state_set_y (y)
	
state_get_y ()
	
state_set_x (x)
	
state_get_x ()
	

Group procedures by the functionality they implement and the state they access, e.g. by naming conventions …

Advantages:

Designing with Modular Programming Languages

Designing with Modular Programming Languages

module gui {
    exports:
        radio_menu(labels)
        button_menu(labels)
        check_buttons(menu)
}	

Modular programming introduced modules, higher-level units/modules introduce higher-level abstractions! One can handle a whole module as if it was its interface. Programming language mechanisms for supporting information hiding: interface hides module internals.

Module-based Abstraction

image_browser gui: radio_menu(labels) button_menu(labels) check_buttons(menu) graphics

Abstraction enables us to:

Hence, abstraction is the key to managing complexity.

Abstraction mechanisms enable us to code and design simultaneously!

"Write what you mean."

"The significant problems we face cannot be solved at the same level of thinking we were at when we created them."

Einstein

Let’s “develop” application families with sophisticated GUIs with uniform look and feel with modular programming…

Great variability:
checkbox button, toolbar button, toggle button, radio button …

Modeling variability with modular programming languages appeared complex…

Designing with Object-Oriented Programming Languages

Designing with Object-Oriented Programming Languages

Object-oriented programming languages introduce new abstraction mechanisms:

  • classes,
  • inheritance,
  • subtype polymorphism.
(Still) Dominating Programming Paradigm

The roots of object-oriented programming languages are in the sixties.

Dahl and Nygaard, Simula 64, 68
Dahl and Nygaard

Allan Kay, Smalltalk 70 - 80
Alan Kay

Object-oriented languages are popular because they make it easier to design software and program at the same time.

They allow us to more directly express high level information about design components abstracting over differences of their variants.

Make it easier to produce the design, and easier to refine it later.

With stronger type checking, they also help the process of detecting design errors.

Result in a more robust design, in essence a better engineered design.

[…] improvements in programming techniques and programming languages in particular are overwhelmingly more important than anything else in the software business […] […] programmers are interested in design […] when more expressive programming languages become available, software developers will adopt them.

Jack Reeves, To Code is to Design, C++ Report 1992

Designing with Functional, Object-Oriented Programming Languages

Designing with Functional, Object-Oriented Programming Languages

By fusing object-oriented and functional programming we are provided with further means to raise our abstraction level. This enables us to better express our intention.

Example
Creating an abstraction to express that we want to repeat something n times.

def repeat[T: scala.reflect.ClassTag](times: Int)(f: ⇒ T): Array[T] = {
  val array = new Array[T](times)
  var i = 0
  while (i < times) { array(i) = f; i += 1 }
  array
}

Usage Now, we can express that we want to read in two values from the command line.

val values = repeat(2) { System.in.read() }

Programming Languages are not a Panacea

Programming Languages are not a Panacea

We need good style to cope with complexity!

Cover AgileDevelopment Cover DesignHeuristics Cover DesignPatterns Cover OOADwithApplications Cover HeadsFirstDesignPatterns Cover PatternsOfEAA Cover SystemOfPatterns ...

Help is provided through established practices and techniques, design patterns and principles.

Good style can only be recommended, not enforced!

Eventually style rules will have to be turned into language features to be really effective.

General Design Principles

The following principles apply at various abstraction levels:

  • Keep it short and simple
  • Don't repeat yourself (also just called "DRY-Principle")
  • High Cohesion
  • Low Coupling
  • No cyclic dependencies
  • Make it testable
  • Open-closed Design Principle
  • Make it explicit/use Code
  • Keep related things together
  • Keep simple things simple
  • Common-reuse/Common-closure/Reuse-release principles

Object-Oriented Design Principles

  • Liskov Substitution Principle
  • Responsibility Driven Design
  • ...

Design Constraints

  • Conway's Law:

A system's design is constrained by the organization's communication structure.