I think the first thing to realize is that there is a difference between being Agile and being agile. Slowly rolling out agile techniques and characteristics - cross-functional teams, adaptive planning, evolutionary/incremental delivery, time-boxed iterations, and even introducing concepts from Lean are very different than introducing Extreme Programming, Scrum, or Crystal.
You explicitly mention customer involvement. Yes, many of the Agile methodologies call for customer involvement, but that's not required to be agile. In every government/defense related program, I've always had a program or project manager who was the point of contact with the customer. This person becomes the "voice of the customer". It might be slowed down as they teleconference or email or call and clarify, but you can have a single person (or a group, if you have deputy PMs as well) that is the customer representative of your team. Admittedly, it's not quite the same. But isn't being agile about being flexible and responding to change?
You also mention a few key concepts: predefined requirements, having feature requests "thrown over the wall", a lack of prioritization because "they are all important", and fixed-cost and/or fixed-schedule projects. Each of these can be addressed in different ways.
If you think you have all of your requirements up front, chances are you don't. Requirements do change. Just because you have a "finished and signed off" specification doesn't mean it is set in stone. Given whatever requirements document you have, capture them how you feel comfortable and/or in the manner specified by the contract and deliver the requirements, the design, and the architecture. In addition, see if you can treat these are living documents (a design document I saw today at work is labeled as Revision G, which means it's on it's 8th update). Ask about how much you can leave as TBD in any given iteration and how much needs to be firmed up now - there might be some give and take.
Be agile with your documentation. Don't duplicate efforts between "what your team wants" and "what the customer wants". For example, if your customer wants a traditional software requirements specification and your team wants to use user stories, try to adapt to a traditional SRS and use action items and a rolling action item list instead of user stories so that you don't spend time formulating both "the system shall..." and " must be able to because ". This does take discipline on the part of the team, though, to adapt to differences between projects. Capture problems in reflections.
Once you get to development, you might run 5 or 6 iterations, and then invite your customer to your facility to see a subset of your implementation. Rinse and repeat this process. It's not the constant involvement demanded by some methodologies, but you do have the advantage of high visibility. If your customer says no, at least you tried. If they say yes, you can enlighten them on being agile. On one project I was on, the customer visited the site every few months (3-5 months, usually). They would watch us go through QA testing, they would discuss concerns with engineers, and business with the program/project office. It was an opportunity for everyone to get on the same page.
Testing and maintenance happen the same as on other agile project. Create your test procedures and document defects in the appropriate way, track metrics per contractual obligations, and document test results. If you want to follow TDD, go for it. Continuous integration is another good idea. During project status meetings, your project manager can use this information to say "we implemented N requirements, have M tests, X tests pass" and update on project health and status to the people with the money.
Speaking of money, we have the fixed-cost and/or fixed-schedule problem.
Dealing with a fixed schedule is fairly straightforward. Given your requirements, you know how many iterations that you can complete. Your workload for each iteration is pretty much set in stone in terms of features to implement, test, and integrate. It might be difficult, but it's not impossible to break up features and assign them to iterations in advance. This goes back to my point about inviting the customer - if you have one year and are using 2 week iterations, perhaps invite the customer quarterly (and invite them every quarter) and show them the results of the previous work. Let them see your prioritization of requirements, your future plans, and how you are going about scheduling.
Dealing with a fixed budget is similar. You know how much time you have, how many resources you have for the project, how much they cost, and therefore how many hours everyone can work per iteration. It's just a matter of ensuring that everyone keeps track of this carefully. If your company can eat the cost of overtime, go for it. Otherwise, make sure everyone works the appropriate length of time and use good time management skills and time-boxing to keep everyone productive. More productive hours is what you need to keep costs down - deliver more value-adding documents and software without the cost of meetings and overhead.
Ultimately, it's not about necessarily being Agile, but applying the things that make Agile good and being agile. Be able to respond to changes in requirements, be able to deliver frequent software even if the customer doesn't want it, only produce value-adding documentation (along with whatever you are contractually obligated to produce), and so on.
Smalltalk - a highly dynamic language - has always had a superb IDE, in which little things like automatic refactoring, SUnit (the grandparent of all *Unit frameworks), "who sends this message?", "who implements this method?" and the like were pioneered. So yes, IDEs can indeed support dynamic languages to a level that, until very recently, far surpassed those of statically typed languages.
S. Lott says that "Dynamic languages cannot have the same kind of code completion that static languages do. It's essentially impossible."
"Essentially impossible" in a theoretical, absolutist sense? Sure. In a practical sense? Not really. There are many ways to infer type for dynamic languages, statically (k-CFA, RoelTyper), at run time (PICs), and other methods, like using a programmer's recent history - "How Program History Can Improve Code Completion".
Certainly the Pharo and Squeak communities are very happy with their code completion tools.
Best Answer
There are a number of 'neat' things that can be done in dynamic languages that can be tucked away in parts of the code that aren't immediately obvious to another programmer or auditor as to the functionality of a given piece of code.
Consider this sequence in irb (interactive ruby shell):
What happened there is I tried to call the method
foo
in a String constant. This failed. I then opened up the String class and defined the methodfoo
o return"foobar!"
, and then called it. This worked.This is known as an open class and gives me nightmares every time I think of writing code in ruby that has any sort of security or integrity. Sure it lets you do some neat things quite fast... but I could make it so every time someone stored a string, it stored it to a file, or sent it over the network. And this little bit of redefining the String can be tucked anywhere in the code.
Many other dynamic languages have similar things that can be done. Perl has Tie::Scalar that can behind the scenes change how a given scalar works (this is a bit more obvious and requires a specific command that you can see, but a scalar that is passed in from somewhere else could be a problem). If you have access to the Perl Cookbook, look up Recipe 13.15 - Creating Magic Variables with tie.
Because of these things (and others often part of dynamic languages), many approaches to static analysis of security in code doesn't work. Perl and Undecidability shows this to be the case and points out even such trivial problems with syntax highlighting (
whatever / 25 ; # / ; die "this dies!";
poses challenges because thewhatever
can be defined to take arguments or not at runtime completely defeating a syntax highlighter or static analyzer).This can get even more interesting in Ruby with the ability to access the environment that a closure was defined in (see YouTube: Keeping Ruby Reasonable from RubyConf 2011 by Joshua Ballanco). I was made aware of this video from an Ars Technica comment by MouseTheLuckyDog.
Consider the following code:
This code is fully visible, but the
mal
method could be somewhere else... and with open classes, of course, it could be redefined somewhere else.Running this code:
In this code, the closure was able to access all of the methods and other bindings defined in the class at that scope. It picked a random method and redefined it to raise an exception. (see the Binding class in Ruby to get an idea of what this object has access to)
A shorter version that shows the redefinition of a variable:
Which, when run produces:
This is more than the open class that I mentioned above that makes static analysis impossible. What is demonstrated above is that a closure that is passed somewhere else, carries with it the full environment that it was defined in. This is known as a first class environment (just as when you can pass around functions, they are first class functions, this is the environment and all of the bindings available at that time). One could redefine any variable that was defined in the scope of the closure.
Good or bad, complaining about ruby or not (there are uses where one would want to be able to get at the environment of a method (see Safe in Perl)), the question of "why would ruby be restricted in for a government project" really is answered in that video linked above.
Given that:
With the implications of these four design choices, it is impossible to know what any bit of code does.
More about this can be read at Abstract Heresies blog. The particular post is about Scheme where such a debate was had. (related on SO: Why doesn't Scheme support first class environments?)
I hope this section shows the danger aspect of first class environments and why it would be asked to remove Ruby from the provided solution. Its not just that Ruby is a dynamic language (as mentioned else-answer, other dynamic languages have been allowed in other projects), but that there are specific issues that make some dynamic languages even more difficult to reason about.