`
|
|
Archive for the ‘Agile’ Category
Saturday, January 11th, 2014
Come, join the very FIRST Agile Job Fair in the World!
A platform dedicated for the Agile practitioners to meet their potential Agile employers.
Agile India Job Fair is being organised by Agile Software Community of India, a registered non-profit society. We have been running conference and other events in India since 2004. This job fair is on the very next day, after our international conference – Agile India 2014, which attracts about 1000 international participants.
Why a job fair?
Agile methods have become mainstream and they are here to stay. In India, many companies are having a hard time finding needles in the haystack .i.e. finding really good Agile practitioners from a whole lot of posers.
The few, really good practitioners out there, have a similar problem. Every company wants to hire Agile people, but are they ready? Do they really believe in Agile culture and even have an agile mindset?
Many practitioners want to talk to real people from the company to really understand the culture of the organisation and the nature of the work.
Browsing the classifieds or surfing the Internet or talking to headhunters (recruiting companies) can only get you so far.
To solve this problem, we are creating a first-of-its-kind, unique opportunity where job-seekers can meet several top Agile employers face-to-face under one roof, clarify their doubts, interview with potential companies and also socialise with other candidates.
Walk-In to explore a gamut of Agile career opportunities with the best Agile employers in India.
What kind of candidates would this event attract?
We have a database of 56,512 software professionals from top companies in India. We will market this event to all these folks. However, who will attend will largely depend on the kind of companies that will be participating to hire candidates. We would filter the companies, to make sure only top companies are part of this event and hence ensure that we would be able to attract really good practitioners.
What can a participating Company (Employer) do to attract participants?
Seeing is believing! So we would strongly suggest you give participants a glimpse of your work culture. May be setup a pair-programming station and project the programming session on a large screen. May be you can setup a story card wall. Showcase some the nature of problems your company is solving. Run a slideshow of pictures from your office. And may more. Just get creative!
What is the cost to participate?
This is a non-profit event. There are 2 major costs, the hall rental and the cost of setting up stalls. We would pass the actual cost to the companies. Our estimate is 35,000-50,000 INR per company. And we are planning to keep it free for Job Seekers (Agile Practitioners) to attend.
Exclusive Event: Get FREE agile career counseling and coaching from Naresh Jain, the founder of the Agile movement in India.
Sounds interesting? Fill the form to participate…Agile Job Fair
Posted in Agile, agile india, Community, Conference, Interview Process | 1 Comment »
Wednesday, December 18th, 2013
There is an old saying that “Practice makes perfect“. But not all practice can make you perfect. There is a specific type of practice usually referred to as deliberate practice or deep practice which can really help you master the skill at hand.
Geoff Colvin, the author of the groundbreaking bestseller Talent Is Overrated explains that deliberate practice can be described by these five characteristics:
- It’s designed specifically to improve performance
- It can be repeated a lot
- Feedback on actions is continuously available
- It’s highly demanding mentally
- It isn’t much fun
Hence a short, focused and regular practice session is lot more effective than one long, generic, random practice session.
Its also important to note that deliberate/deep practice requires a certain context or mindset to really facilitate the learning. Following are important characteristics:
- You are slightly off-balance, trying to get back
- You are constantly getting tons of clear, instant feedback
- You are at the edge of your ability, but motivated to stretch yourself a little bit more
- You are staring at you role models, .i.e. you’ve a clear idea of your goals or the kind of person you want to become.
The advantage of deliberate practice is cumulative, hence starting early has a big advantage.
Generally, I used to be against rote learning, but now I’m rethinking through my conclusions.
If this topic interests you, I would strongly recommend the following books:
Some good videos to start on this topic:
Posted in Cognitive Science, Learning, Training | No Comments »
Thursday, December 5th, 2013
I remember back in the days, before any mocking frameworks existed in Java, we used to create an anonymous-inner class of an abstract class to fake-out the abstract method’s behaviour and use the real logic of the concrete method.
This worked fine, except in cases where we had a lot of abstract methods and overriding each of those methods to do-nothing or return dummy value seemed like a complete waste of time.
With the mocking frameworks like Mockito, we have a better way to deal with such situations, esp. in legacy code. But there is a catch. Let me explain it to you via a code example.
public abstract class AbstractClazz {
public String sayHello() {
return "Hello";
}
}
public class AbstractClazzTest {
@Test
public void shouldCallRealMethods() {
AbstractClazz clazz = mock(AbstractClazz.class);
assertEquals("Hello", clazz.sayHello());
}
} |
public abstract class AbstractClazz {
public String sayHello() {
return "Hello";
}
}
public class AbstractClazzTest {
@Test
public void shouldCallRealMethods() {
AbstractClazz clazz = mock(AbstractClazz.class);
assertEquals("Hello", clazz.sayHello());
}
}
This test fails, with the following error:
java.lang.AssertionError: expected:<Hello> but was:<null>
at org.junit.Assert.fail(Assert.java:91)
at org.junit.Assert.failNotEquals(Assert.java:645)
at org.junit.Assert.assertEquals(Assert.java:126)
at org.junit.Assert.assertEquals(Assert.java:145)
at com.agilefaqs.mocking.AbstractClazzTest.
shouldNotFakeRealMethods(AbstractClazzTest.java:20) |
java.lang.AssertionError: expected:<Hello> but was:<null>
at org.junit.Assert.fail(Assert.java:91)
at org.junit.Assert.failNotEquals(Assert.java:645)
at org.junit.Assert.assertEquals(Assert.java:126)
at org.junit.Assert.assertEquals(Assert.java:145)
at com.agilefaqs.mocking.AbstractClazzTest.
shouldNotFakeRealMethods(AbstractClazzTest.java:20)
To make this work, We need to pass the following Answer parameter while creating the Mock:
AbstractClazz clazz = mock(AbstractClazz.class, CALLS_REAL_METHODS); |
AbstractClazz clazz = mock(AbstractClazz.class, CALLS_REAL_METHODS);
Now let’s say our requirements have evolved. Our sayHello() method should also add the person’s name and greet. Different implementations will figure out different ways to fetch the person’s name.
public abstract class AbstractClazz {
public String sayHello() {
return "Hello " + fetchName() + "!";
}
protected abstract String fetchName();
}
public class AbstractClazzTest {
@Test
public void shouldCallRealMethodsAndFakeAbstractMethod() {
AbstractClazz clazz = mock(AbstractClazz.class, CALLS_REAL_METHODS);
when(clazz.fetchName()).thenReturn("Naresh");
assertEquals("Hello Naresh!", clazz.sayHello());
}
} |
public abstract class AbstractClazz {
public String sayHello() {
return "Hello " + fetchName() + "!";
}
protected abstract String fetchName();
}
public class AbstractClazzTest {
@Test
public void shouldCallRealMethodsAndFakeAbstractMethod() {
AbstractClazz clazz = mock(AbstractClazz.class, CALLS_REAL_METHODS);
when(clazz.fetchName()).thenReturn("Naresh");
assertEquals("Hello Naresh!", clazz.sayHello());
}
}
The moment we run this test, we get the following error:
java.lang.AbstractMethodError:
com.agilefaqs.mocking.AbstractClazz.fetchName()Ljava/lang/String;
at com.agilefaqs.mocking.AbstractClazzTest.
shouldCallRealMethodsAndFakeAbstractMethod(AbstractClazzTest.java:22) |
java.lang.AbstractMethodError:
com.agilefaqs.mocking.AbstractClazz.fetchName()Ljava/lang/String;
at com.agilefaqs.mocking.AbstractClazzTest.
shouldCallRealMethodsAndFakeAbstractMethod(AbstractClazzTest.java:22)
Basically, we need our mocking framework to give us a mock which allows partial mocking. Which means, for some methods we want the real methods to be invoked and for some, we want to use the fake implementation.
One way to implement this is by creating a default mock and then explicitly setting expectation on real methods.
@Test
public void shouldCallRealMethodsAndFakeAbstractMethod() {
AbstractClazz clazz = mock(AbstractClazz.class);
when(clazz.sayHello()).thenCallRealMethod();
when(clazz.fetchName()).thenReturn("Naresh");
assertEquals("Hello Naresh!", clazz.sayHello());
} |
@Test
public void shouldCallRealMethodsAndFakeAbstractMethod() {
AbstractClazz clazz = mock(AbstractClazz.class);
when(clazz.sayHello()).thenCallRealMethod();
when(clazz.fetchName()).thenReturn("Naresh");
assertEquals("Hello Naresh!", clazz.sayHello());
}
But the main problem with this approach is that we need to ensure we set explicit expectations on all public and protected methods which might be internally called by the main method (sayHello in this case.) To make matters worse, private methods can’t be mocked and hence we can’t set expectations on them. But let’s say at a later point, if someone makes a private method protected/public, the test will fail, as it will now get mocked. Overall this strategy can make your tests extremely fragile.
For example the following works:
public abstract class AbstractClazz {
public String sayHello() {
return "Hello " + fetchName() + closingSymbol();
}
private String closingSymbol() {
return "!";
}
protected abstract String fetchName();
}
public class AbstractClazzTest {
@Test
public void shouldCallRealMethodsAndFakeAbstractMethod() {
AbstractClazz clazz = mock(AbstractClazz.class);
when(clazz.sayHello()).thenCallRealMethod();
when(clazz.fetchName()).thenReturn("Naresh");
assertEquals("Hello Naresh!", clazz.sayHello());
}
} |
public abstract class AbstractClazz {
public String sayHello() {
return "Hello " + fetchName() + closingSymbol();
}
private String closingSymbol() {
return "!";
}
protected abstract String fetchName();
}
public class AbstractClazzTest {
@Test
public void shouldCallRealMethodsAndFakeAbstractMethod() {
AbstractClazz clazz = mock(AbstractClazz.class);
when(clazz.sayHello()).thenCallRealMethod();
when(clazz.fetchName()).thenReturn("Naresh");
assertEquals("Hello Naresh!", clazz.sayHello());
}
}
However if we change closingSymbol() method to protected/public, the test will fail with the following error:
org.junit.ComparisonFailure: expected:<Hello Naresh[!]> but was:<Hello Naresh[null]> |
org.junit.ComparisonFailure: expected:<Hello Naresh[!]> but was:<Hello Naresh[null]>
A better approach in Mockito is to pass a Custom Answer parameter while creating the mock. Following is the Answer implementation which can do partial mocking:
public class AbstractMethodMocker implements Answer<Object> {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Answer<Object> answer;
if (isAbstract(invocation.getMethod().getModifiers()))
answer = RETURNS_DEFAULTS;
else
answer = CALLS_REAL_METHODS;
return answer.answer(invocation);
}
}
public class AbstractClazzTest {
@Test
public void shouldCallRealMethodsAndFakeAbstractMethod() {
AbstractClazz clazz = mock(AbstractClazz.class, new AbstractMethodMocker());
when(clazz.fetchName()).thenReturn("Naresh");
assertEquals("Hello Naresh!", clazz.sayHello());
}
} |
public class AbstractMethodMocker implements Answer<Object> {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Answer<Object> answer;
if (isAbstract(invocation.getMethod().getModifiers()))
answer = RETURNS_DEFAULTS;
else
answer = CALLS_REAL_METHODS;
return answer.answer(invocation);
}
}
public class AbstractClazzTest {
@Test
public void shouldCallRealMethodsAndFakeAbstractMethod() {
AbstractClazz clazz = mock(AbstractClazz.class, new AbstractMethodMocker());
when(clazz.fetchName()).thenReturn("Naresh");
assertEquals("Hello Naresh!", clazz.sayHello());
}
}
If you are forced to use this technique in brand new code you are building, may I suggest the Delete button…
Posted in Agile, Java, Testing | 2 Comments »
Monday, October 28th, 2013
In the Agile India Submission system, we had a feature which would accept different url path to show matching proposals.
For example:
http://present.agileindia.org/agile-india-2014/agile-lifecycle/45_mins –> will return all 45 mins proposals under the Agile-Lifecycle track for the Agile India 2014 Conference.
http://present.agileindia.org/agile-india-2014/beyond-agile/90_mins –> will return all 90 mins proposals under the Beyond-Agile track for the Agile India 2014 Conference.
If we remove the last part .i.e. http://present.agileindia.org/agile-india-2014/agile-lifecycle –> will return all proposals under the Agile-Lifecycle track for the Agile India 2014 Conference.
and http://present.agileindia.org/agile-india-2014 –> will return all proposals for the Agile India 2014 Conference (could be other conferences as well.)
To achieve this, we had the following routes defined:
//URL Path = /agile-india-2014
app\get("/{conference}", function($req) {
$query_params = structure_request_params($req['matches']);
...
});
//URL Path = /agile-india-2014/agile-lifecycle
app\get("/{conference}/{track}", function($req) {
$query_params = structure_request_params($req['matches']);
...
});
//URL Path = /agile-india-2014/agile-lifecycle/45_mins
app\get("/{conference}/{track}/{label}", function($req) {
$query_params = structure_request_params($req['matches']);
...
}); |
//URL Path = /agile-india-2014
app\get("/{conference}", function($req) {
$query_params = structure_request_params($req['matches']);
...
});
//URL Path = /agile-india-2014/agile-lifecycle
app\get("/{conference}/{track}", function($req) {
$query_params = structure_request_params($req['matches']);
...
});
//URL Path = /agile-india-2014/agile-lifecycle/45_mins
app\get("/{conference}/{track}/{label}", function($req) {
$query_params = structure_request_params($req['matches']);
...
});
In our database we had the following:
CREATE TABLE `conference` (
`key` VARCHAR(255) NOT NULL,
...
PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `track` (
`conference` VARCHAR(255) NOT NULL,
`key` VARCHAR(255) NOT NULL,
...
PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `label` (
`conference` VARCHAR(255) NOT NULL,
`track` VARCHAR(255) NOT NULL,
`key` VARCHAR(255) NOT NULL,
...
PRIMARY KEY (`conference`,`track`,`key`)
) ENGINE= InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `conference` VALUES ('agile-india-2014', ...);
INSERT INTO `track` VALUES ('agile-india-2014', 'agile-lifecycle', ...);
INSERT INTO `label` VALUES ('agile-india-2014', 'agile-lifecycle', '45_mins', ...); |
CREATE TABLE `conference` (
`key` varchar(255) NOT NULL,
...
PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `track` (
`conference` varchar(255) NOT NULL,
`key` varchar(255) NOT NULL,
...
PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `label` (
`conference` varchar(255) NOT NULL,
`track` varchar(255) NOT NULL,
`key` varchar(255) NOT NULL,
...
PRIMARY KEY (`conference`,`track`,`key`)
) ENGINE= InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `conference` VALUES ('agile-india-2014', ...);
INSERT INTO `track` VALUES ('agile-india-2014', 'agile-lifecycle', ...);
INSERT INTO `label` VALUES ('agile-india-2014', 'agile-lifecycle', '45_mins', ...);
And some really complicated logic:
// For example URL Path /agile-india-2014 will result in
// $req_params = array('conference'=>'agile-india-2014')
// And URL Path /agile-india-2014/agile-lifecycle will result in
// $req_params = array('conference'=>'agile-india-2014', 'track'=>'agile-lifecycle')
// And URL Path /agile-india-2014/agile-lifecycle/45_mins will result in
// $req_params = array('conference'=>'agile-india-2014', 'track'=>'agile-lifecycle', 'label'=>'45_mins') |
// For example URL Path /agile-india-2014 will result in
// $req_params = array('conference'=>'agile-india-2014')
// And URL Path /agile-india-2014/agile-lifecycle will result in
// $req_params = array('conference'=>'agile-india-2014', 'track'=>'agile-lifecycle')
// And URL Path /agile-india-2014/agile-lifecycle/45_mins will result in
// $req_params = array('conference'=>'agile-india-2014', 'track'=>'agile-lifecycle', 'label'=>'45_mins')
function structure_request_params($req_params){
$query_params = array();
if(isset($req_params['label'])){
if(Conference::exists($req_params['conference'])){
$query_params['conference'] = $req_params['conference'];
if(Track::exists(array('conference' => $req_params['conference'], 'key' => $req_params['track']))){
$query_params['track'] = $req_params['track'];
if(Label::exists(array(
'conference' => $req_params['conference'],
'track' => $req_params['track'],
'key' => $req_params['label'])
)) {
$query_params['label'] = $req_params['label'];
}
else{
$query_params['search_term'] = array($req_params['label']);
}
}
else{
$query_params['search_term'] = array($req_params['track'], $req_params['label']);
}
}
else{
$query_params['search_term'] = array($req_params['conference'], $req_params['track'], $req_params['label']);
}
}
else{
if(isset($req_params['track'])){
if(Conference::exists($req_params['conference'])){
$query_params['conference'] = $req_params['conference'];
if(Track::exists(array('conference' => $req_params['conference'], 'key' => $req_params['track']))){
$query_params['track'] = $req_params['track'];
}
else{
$query_params['search_term'] = array($req_params['track']);
}
}
else{
if(Track::exists(array('key' => $req_params['conference']))){
$query_params['track'] = $req_params['conference'];
if(Label::exists(array('track' => $req_params['conference'], 'key' => $req_params['track']))){
$query_params['label'] = $req_params['track'];
}
else{
$query_params['search_term'] = array($req_params['track']);
}
}
else{
$query_params['search_term'] = array($req_params['conference'], $req_params['track']);
}
}
}
else{
if(isset($req_params['conference'])){
if(Conference::exists($req_params['conference'])){
$query_params['conference'] = $req_params['conference'];
}
elseif(Track::exists(array('key' => $req_params['conference']))){
$query_params['track'] = $req_params['conference'];
}
elseif(Label::exists(array('key' => $req_params['conference']))){
$query_params['label'] = $req_params['conference'];
}
else{
$query_params['search_term'] = array($req_params['conference']);
}
}
}
}
return $query_params;
} |
function structure_request_params($req_params){
$query_params = array();
if(isset($req_params['label'])){
if(Conference::exists($req_params['conference'])){
$query_params['conference'] = $req_params['conference'];
if(Track::exists(array('conference' => $req_params['conference'], 'key' => $req_params['track']))){
$query_params['track'] = $req_params['track'];
if(Label::exists(array(
'conference' => $req_params['conference'],
'track' => $req_params['track'],
'key' => $req_params['label'])
)) {
$query_params['label'] = $req_params['label'];
}
else{
$query_params['search_term'] = array($req_params['label']);
}
}
else{
$query_params['search_term'] = array($req_params['track'], $req_params['label']);
}
}
else{
$query_params['search_term'] = array($req_params['conference'], $req_params['track'], $req_params['label']);
}
}
else{
if(isset($req_params['track'])){
if(Conference::exists($req_params['conference'])){
$query_params['conference'] = $req_params['conference'];
if(Track::exists(array('conference' => $req_params['conference'], 'key' => $req_params['track']))){
$query_params['track'] = $req_params['track'];
}
else{
$query_params['search_term'] = array($req_params['track']);
}
}
else{
if(Track::exists(array('key' => $req_params['conference']))){
$query_params['track'] = $req_params['conference'];
if(Label::exists(array('track' => $req_params['conference'], 'key' => $req_params['track']))){
$query_params['label'] = $req_params['track'];
}
else{
$query_params['search_term'] = array($req_params['track']);
}
}
else{
$query_params['search_term'] = array($req_params['conference'], $req_params['track']);
}
}
}
else{
if(isset($req_params['conference'])){
if(Conference::exists($req_params['conference'])){
$query_params['conference'] = $req_params['conference'];
}
elseif(Track::exists(array('key' => $req_params['conference']))){
$query_params['track'] = $req_params['conference'];
}
elseif(Label::exists(array('key' => $req_params['conference']))){
$query_params['label'] = $req_params['conference'];
}
else{
$query_params['search_term'] = array($req_params['conference']);
}
}
}
}
return $query_params;
}
And supporting model classes:
class Conference{
static function exists($conference){
DB::query('SELECT * FROM conference WHERE conference.key = %s', $conference);
return (DB::count() > 0);
}
}
class Track{
static function exists($query_params){
$search_query = Track::create_search_query($query_params);
DB::query("SELECT * FROM track WHERE $search_query");
return (DB::count() > 0);
}
private static function create_search_query($query_params){
$search_query = array();
foreach($query_params as $key => $value){
$search_query[] = "$key='$value'";
}
return join(' AND ', $search_query);
}
}
class Label{
static function exists($query_params){
$search_query = Label::create_search_query($query_params);
DB::query("SELECT * FROM label WHERE $search_query");
return (DB::count() > 0);
}
private static function create_search_query($query_params){
$search_query = array();
if(!empty($query_params)){
foreach($query_params as $key => $value){
$search_query[] = "$key='$value'";
}
return join(' AND ', $search_query);
}
return '';
}
} |
class Conference{
static function exists($conference){
DB::query('SELECT * FROM conference WHERE conference.key = %s', $conference);
return (DB::count() > 0);
}
}
class Track{
static function exists($query_params){
$search_query = Track::create_search_query($query_params);
DB::query("SELECT * FROM track WHERE $search_query");
return (DB::count() > 0);
}
private static function create_search_query($query_params){
$search_query = array();
foreach($query_params as $key => $value){
$search_query[] = "$key='$value'";
}
return join(' AND ', $search_query);
}
}
class Label{
static function exists($query_params){
$search_query = Label::create_search_query($query_params);
DB::query("SELECT * FROM label WHERE $search_query");
return (DB::count() > 0);
}
private static function create_search_query($query_params){
$search_query = array();
if(!empty($query_params)){
foreach($query_params as $key => $value){
$search_query[] = "$key='$value'";
}
return join(' AND ', $search_query);
}
return '';
}
}
This was extremely hard to understand, so I decided to clean it up and get rid of the conditional complexity code smell.
Refactored code:
function structure_request_params($req_params){
$criteria = array('conference', 'track', 'label');
$params = array_values($req_params);
return build_search_query_params($criteria, $params, array());
}
function build_search_query_params($criteria, $params, $results) {
if(empty($params)) return $results;
if(empty($criteria)) {
if(!empty($params)) {
$results['search_term'] = $params;
}
return $results;
}
if(is_present_in_db($criteria[0], $params[0])) {
$results[$criteria[0]] = $params[0];
array_shift($params);
}
array_shift($criteria);
return map_criteria_params($criteria, $params, $results);
}
function is_present_in_db($table_name, $value){
return (DB::queryFirstField("SELECT count(*) FROM $table_name WHERE `key` = %s", $value) > 0);
} |
function structure_request_params($req_params){
$criteria = array('conference', 'track', 'label');
$params = array_values($req_params);
return build_search_query_params($criteria, $params, array());
}
function build_search_query_params($criteria, $params, $results) {
if(empty($params)) return $results;
if(empty($criteria)) {
if(!empty($params)) {
$results['search_term'] = $params;
}
return $results;
}
if(is_present_in_db($criteria[0], $params[0])) {
$results[$criteria[0]] = $params[0];
array_shift($params);
}
array_shift($criteria);
return map_criteria_params($criteria, $params, $results);
}
function is_present_in_db($table_name, $value){
return (DB::queryFirstField("SELECT count(*) FROM $table_name WHERE `key` = %s", $value) > 0);
}
Also with this, we were able to delete the Model classes as they were not required.
Posted in Agile, Code Smells, Design, Programming | No Comments »
Friday, October 18th, 2013
Draft Schedule; only for early feedback purpose. Likely to change.
Direct Link
Please email Pramod Sadalage, Conference Program Chair with your suggestions.
Posted in Agile, agile india | No Comments »
Thursday, October 10th, 2013
At the Agile Goa conference, I ran a small workshop to help participants understand the core techniques one should master to effectively practice evolutionary design while solving real-world problems.
Key take aways:
- Eliminate Noise – Distill down the crux of the problem
- Divide and Conquer – Focus on one scenario at a time and incrementally build your solution
- Add constraints to future simplify the problem
- Simple Design – Find the simplest possible solution to solve the current scenario at hand
- Refactor: Pause, look for a much simpler alternative
- Be ready to throw away your solution and start again
Posted in Agile, agile india, Design, Learning | No Comments »
Thursday, September 19th, 2013
Posted in Agile, agile india | No Comments »
Thursday, September 12th, 2013
Why do we see new process or methodology or movement every 10 years or so?
Using the Adaptive Change Cycle we can easily explain the rationale behind it.
Posted in Agile, Lean Startup | No Comments »
Thursday, September 5th, 2013
Posted in Agile, agile india, Community, Conference | No Comments »
Thursday, September 5th, 2013
Register for the three existing conferences coming up in India:
1. Agile Goa 2013 – Our 6th Annual Conference in Goa
We’ve a superb speaker lineup. Check out the program
Limited seats available, register today at: http://booking.agilefaqs.com/agile-goa-2013
2. Agile Kerala 2013 – FIRST ever Agile and Lean Conference in Kerala. Check out our Planned Program
Take advantage of the Early-Bird pricing, register today at: http://booking.agilefaqs.com/agile-kerala-2013
3. Agile India 2014 – Asia’s Premier and Largest conference on Agile and Lean Methods. Get an opportunity to meet Martin Fowler, Dave Thomas, Dave Snowden, Ash Maurya and many other thought leaders…
We launched the online registration on Sep 2nd at 10:00 AM. In a matter of 40 mins, the entire Super Early Bird Registration Slab of 100 tickets was completely SOLD OUT. This is the best response we’ve got in the last 9 years of organizing these conferences.
Don’t miss this unique opportunity to grab a conference ticket at these special, limited-time, attractive prices – http://booking.agilefaqs.com/agile-india-2014
Posted in Agile, agile india, Community, Conference | No Comments »
|